├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build-test.yml │ ├── codeql-analysis.yml │ ├── flawfinder-analysis.yml │ ├── pylint.yml │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── pylintrc ├── python_wireguard ├── .gitignore ├── __init__.py ├── bin │ └── .gitkeep ├── client.py ├── client_connection.py ├── key.py ├── server.py ├── server_connection.py └── wireguard.py ├── setup.py ├── src ├── .gitignore ├── python_interface.c ├── wireguard.c └── wireguard.h ├── test_requirements.txt └── tests ├── .gitignore ├── context.py ├── test_client.py ├── test_client_connection.py ├── test_keys.py ├── test_server.py ├── test_server_connection.py └── test_wireguard.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Linux] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[REQUEST]" 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | assignees: 13 | - "jarnoaxel" 14 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-and-test: 7 | name: Build and test (Python ${{ matrix.python-version }}) 8 | 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | python-version: ["3.8", "3.9", "3.10"] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install Wireguard 18 | run: sudo apt update && sudo apt install wireguard 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Build shared library 24 | run: make 25 | - name: Install dependencies 26 | run: | 27 | sudo python -m pip install --upgrade pip 28 | sudo python -m pip install -r test_requirements.txt 29 | if [ -f requirements.txt ]; then sudo pip install -r requirements.txt; fi 30 | - name: Test with pytest 31 | run: | 32 | sudo pytest --junit-xml pytest.xml --cov-report term-missing --cov-fail-under=90 --cov=python_wireguard tests/ 33 | - name: Upload Unit Test Results 34 | if: always() 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: Unit Test Results (Python ${{ matrix.python-version }}) 38 | path: pytest.xml 39 | 40 | publish-test-results: 41 | name: "Publish Unit Tests Results" 42 | needs: build-and-test 43 | runs-on: ubuntu-latest 44 | if: always() 45 | 46 | steps: 47 | - name: Download Artifacts 48 | uses: actions/download-artifact@v2 49 | with: 50 | path: artifacts 51 | 52 | - name: Publish Unit Test Results 53 | uses: EnricoMi/publish-unit-test-result-action@v1 54 | with: 55 | files: artifacts/**/*.xml 56 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '41 23 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'cpp', 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/flawfinder-analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: flawfinder 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ main ] 14 | schedule: 15 | - cron: '28 3 * * 0' 16 | 17 | jobs: 18 | flawfinder: 19 | name: Flawfinder 20 | runs-on: ubuntu-latest 21 | permissions: 22 | actions: read 23 | contents: read 24 | security-events: write 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | 29 | - name: flawfinder_scan 30 | uses: david-a-wheeler/flawfinder@8e4a779ad59dbfaee5da586aa9210853b701959c 31 | with: 32 | arguments: '--sarif ./' 33 | output: 'flawfinder_results.sarif' 34 | 35 | - name: Upload analysis results to GitHub Security tab 36 | uses: github/codeql-action/upload-sarif@v1 37 | with: 38 | sarif_file: ${{github.workspace}}/flawfinder_results.sarif 39 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install pylint 21 | - name: Analysing the code with pylint 22 | run: | 23 | pylint $(git ls-files '*.py') 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: PyPI deployment 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | jobs: 16 | deploy: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.x' 26 | - name: Build shared library 27 | run: make 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install build 32 | - name: Build package 33 | env: 34 | RELEASE_VERSION: ${{ github.event.release.tag_name }} 35 | run: python -m build 36 | - name: Publish package 37 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 38 | with: 39 | user: __token__ 40 | password: ${{ secrets.PYPI_API_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.egg-info/* 3 | dist 4 | dist/* 5 | .coverage 6 | pytest.xml 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include python_wireguard/bin/py-wireguard.so 2 | include README.md -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS = -Wall -g -O -fPIC $(shell pkg-config --cflags python3) 3 | RM= rm -f 4 | .PHONY: all clean 5 | 6 | all: python_wireguard/bin/py-wireguard.so 7 | clean: 8 | $(RM) src/*.o python_wireguard/bin/*.so 9 | 10 | python_wireguard/bin/py-wireguard.so: src/python_interface.o src/wireguard.o 11 | $(LINK.c) -shared $^ -o $@ 12 | 13 | src/python_interface.o: src/python_interface.c 14 | 15 | src/wireguard.o: src/wireguard.c src/wireguard.h 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Wireguard interface 2 | Library for controlling Wireguard using python. 3 | 4 | ![security badge](https://github.com/jarnoaxel/python-wireguard/actions/workflows/codeql-analysis.yml/badge.svg) 5 | ![test badge](https://github.com/jarnoaxel/python-wireguard/actions/workflows/build-test.yml/badge.svg) 6 | ![linter badge](https://github.com/jarnoaxel/python-wireguard/actions/workflows/pylint.yml/badge.svg) 7 | ![package badge](https://github.com/jarnoaxel/python-wireguard/actions/workflows/python-publish.yml/badge.svg) 8 | 9 | ## Installation 10 | To install this package, use pip: 11 | 12 | ```bash 13 | pip install python_wireguard 14 | ``` 15 | 16 | ## Security remark 17 | Changing network interface settings, and interacting with Wireguard, is only possible as the root user by default. 18 | 19 | ## Usage 20 | This package was designed with a client/server infrastructure in mind. This differs from the 'default' usage of Wireguard, which is peer to peer. Because of this, there is a different set of functions required depending on whether you are writing client-side code or server-side code. We will now discuss an example workflow for setting up a client-server connection to Wireguard. 21 | 22 | ### Generate key pair 23 | Both the client and the server need a key pair. 24 | 25 | ```python 26 | from python_wireguard import Key 27 | private, public = Key.key_pair() 28 | public 29 | # 30 | print(public) 31 | # 5TxYUa403l9A9yEVMyIsSZwae4C7497IT8uaMYEdLHQ= 32 | ``` 33 | 34 | Creating a key from a base64 string is also possible, which is useful for creating one for the other device's public key: 35 | ```python 36 | from python_wireguard import Key 37 | srv_public = Key("some string containing a base64 key") 38 | ``` 39 | 40 | ### Server 41 | This section explains setting up the connection on the server machine. 42 | 43 | ```python 44 | from python_wireguard import Server, ClientConnection 45 | server = Server("wg-srv", private, "10.0.0.1/24", 12345) 46 | server.enable() 47 | ``` 48 | You should now be able to see connection on your machine using the 'normal' wireguard cli: 49 | ```shell 50 | sudo wg 51 | ``` 52 | Example output: 53 | ``` 54 | interface: wg-srv 55 | public key: Z9mHJ0apfgTvULpV3t9jpzyjmABSts1weE2jPiee8w8= 56 | private key: (hidden) 57 | listening port: 12345 58 | ``` 59 | #### Add a client 60 | For adding a client connection, you first need to create a `ClientConnection` object: 61 | ```python 62 | from python_wireguard import ClientConnection, Key 63 | 64 | client_key = Key("base64 string received from client (public key)") 65 | client_ip = "10.0.0.2" # The 'local' ip address that the client will be assigned. 66 | conn = ClientConnection(client_key, client_ip) 67 | ``` 68 | 69 | You can now add this client to the server: 70 | ```python 71 | server.add_client(conn) 72 | ``` 73 | 74 | ### Client 75 | This section explains setting up the connection on a client machine. This needs to be a different machine than the server machine. 76 | ```python 77 | from python_wireguard import Client, ServerConnection, Key 78 | 79 | local_ip = "10.0.0.2/24" # CIDR block received from server. 80 | 81 | client = Client('wg-client', private, local_ip) 82 | 83 | srv_key = Key("base64 string received from the server (public key)") 84 | endpoint = "public ip address of the server" 85 | port = 12345 # The port on which the server has been set up to listen 86 | 87 | server_conn = ServerConnection(srv_key, endpoint, port) 88 | 89 | client.set_server(server_conn) 90 | client.connect() 91 | ``` 92 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-allow-list= 7 | 8 | # A comma-separated list of package or module names from where C extensions may 9 | # be loaded. Extensions are loading into the active Python interpreter and may 10 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 11 | # for backward compatibility.) 12 | extension-pkg-whitelist= 13 | 14 | # Return non-zero exit code if any of these messages/categories are detected, 15 | # even if score is above --fail-under value. Syntax same as enable. Messages 16 | # specified are enabled, while categories only check already-enabled messages. 17 | fail-on= 18 | 19 | # Specify a score threshold to be exceeded before program exits with error. 20 | fail-under=10.0 21 | 22 | # Files or directories to be skipped. They should be base names, not paths. 23 | ignore=CVS 24 | 25 | # Add files or directories matching the regex patterns to the ignore-list. The 26 | # regex matches against paths and can be in Posix or Windows format. 27 | ignore-paths=tests/* 28 | 29 | # Files or directories matching the regex patterns are skipped. The regex 30 | # matches against base names, not paths. 31 | ignore-patterns= 32 | 33 | # Python code to execute, usually for sys.path manipulation such as 34 | # pygtk.require(). 35 | #init-hook= 36 | 37 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 38 | # number of processors available to use. 39 | jobs=1 40 | 41 | # Control the amount of potential inferred values when inferring a single 42 | # object. This can help the performance when dealing with large functions or 43 | # complex, nested conditions. 44 | limit-inference-results=100 45 | 46 | # List of plugins (as comma separated values of python module names) to load, 47 | # usually to register additional checkers. 48 | load-plugins= 49 | 50 | # Pickle collected data for later comparisons. 51 | persistent=yes 52 | 53 | # Minimum Python version to use for version dependent checks. Will default to 54 | # the version used to run pylint. 55 | py-version=3.10 56 | 57 | # When enabled, pylint would attempt to guess common misconfiguration and emit 58 | # user-friendly hints instead of false-positive error messages. 59 | suggestion-mode=yes 60 | 61 | # Allow loading of arbitrary C extensions. Extensions are imported into the 62 | # active Python interpreter and may run arbitrary code. 63 | unsafe-load-any-extension=no 64 | 65 | 66 | [MESSAGES CONTROL] 67 | 68 | # Only show warnings with the listed confidence levels. Leave empty to show 69 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 70 | confidence= 71 | 72 | # Disable the message, report, category or checker with the given id(s). You 73 | # can either give multiple identifiers separated by comma (,) or put this 74 | # option multiple times (only on the command line, not in the configuration 75 | # file where it should appear only once). You can also use "--disable=all" to 76 | # disable everything first and then reenable specific checks. For example, if 77 | # you want to run only the similarities checker, you can use "--disable=all 78 | # --enable=similarities". If you want to run only the classes checker, but have 79 | # no Warning level messages displayed, use "--disable=all --enable=classes 80 | # --disable=W". 81 | disable=raw-checker-failed, 82 | bad-inline-option, 83 | locally-disabled, 84 | file-ignored, 85 | suppressed-message, 86 | useless-suppression, 87 | deprecated-pragma, 88 | use-symbolic-message-instead 89 | 90 | # Enable the message, report, category or checker with the given id(s). You can 91 | # either give multiple identifier separated by comma (,) or put this option 92 | # multiple time (only on the command line, not in the configuration file where 93 | # it should appear only once). See also the "--disable" option for examples. 94 | enable=c-extension-no-member 95 | 96 | 97 | [REPORTS] 98 | 99 | # Python expression which should return a score less than or equal to 10. You 100 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 101 | # which contain the number of messages in each category, as well as 'statement' 102 | # which is the total number of statements analyzed. This score is used by the 103 | # global evaluation report (RP0004). 104 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 105 | 106 | # Template used to display messages. This is a python new-style format string 107 | # used to format the message information. See doc for all details. 108 | #msg-template= 109 | 110 | # Set the output format. Available formats are text, parseable, colorized, json 111 | # and msvs (visual studio). You can also give a reporter class, e.g. 112 | # mypackage.mymodule.MyReporterClass. 113 | output-format=text 114 | 115 | # Tells whether to display a full report or only the messages. 116 | reports=no 117 | 118 | # Activate the evaluation score. 119 | score=yes 120 | 121 | 122 | [REFACTORING] 123 | 124 | # Maximum number of nested blocks for function / method body 125 | max-nested-blocks=5 126 | 127 | # Complete name of functions that never returns. When checking for 128 | # inconsistent-return-statements if a never returning function is called then 129 | # it will be considered as an explicit return statement and no message will be 130 | # printed. 131 | never-returning-functions=sys.exit,argparse.parse_error 132 | 133 | 134 | [VARIABLES] 135 | 136 | # List of additional names supposed to be defined in builtins. Remember that 137 | # you should avoid defining new builtins when possible. 138 | additional-builtins= 139 | 140 | # Tells whether unused global variables should be treated as a violation. 141 | allow-global-unused-variables=yes 142 | 143 | # List of names allowed to shadow builtins 144 | allowed-redefined-builtins= 145 | 146 | # List of strings which can identify a callback function by name. A callback 147 | # name must start or end with one of those strings. 148 | callbacks=cb_, 149 | _cb 150 | 151 | # A regular expression matching the name of dummy variables (i.e. expected to 152 | # not be used). 153 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 154 | 155 | # Argument names that match this expression will be ignored. Default to name 156 | # with leading underscore. 157 | ignored-argument-names=_.*|^ignored_|^unused_ 158 | 159 | # Tells whether we should check for unused import in __init__ files. 160 | init-import=no 161 | 162 | # List of qualified module names which can have objects that can redefine 163 | # builtins. 164 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 165 | 166 | 167 | [SIMILARITIES] 168 | 169 | # Comments are removed from the similarity computation 170 | ignore-comments=yes 171 | 172 | # Docstrings are removed from the similarity computation 173 | ignore-docstrings=yes 174 | 175 | # Imports are removed from the similarity computation 176 | ignore-imports=no 177 | 178 | # Signatures are removed from the similarity computation 179 | ignore-signatures=no 180 | 181 | # Minimum lines number of a similarity. 182 | min-similarity-lines=4 183 | 184 | 185 | [LOGGING] 186 | 187 | # The type of string formatting that logging methods do. `old` means using % 188 | # formatting, `new` is for `{}` formatting. 189 | logging-format-style=old 190 | 191 | # Logging modules to check that the string format arguments are in logging 192 | # function parameter format. 193 | logging-modules=logging 194 | 195 | 196 | [TYPECHECK] 197 | 198 | # List of decorators that produce context managers, such as 199 | # contextlib.contextmanager. Add to this list to register other decorators that 200 | # produce valid context managers. 201 | contextmanager-decorators=contextlib.contextmanager 202 | 203 | # List of members which are set dynamically and missed by pylint inference 204 | # system, and so shouldn't trigger E1101 when accessed. Python regular 205 | # expressions are accepted. 206 | generated-members= 207 | 208 | # Tells whether missing members accessed in mixin class should be ignored. A 209 | # class is considered mixin if its name matches the mixin-class-rgx option. 210 | ignore-mixin-members=yes 211 | 212 | # Tells whether to warn about missing members when the owner of the attribute 213 | # is inferred to be None. 214 | ignore-none=yes 215 | 216 | # This flag controls whether pylint should warn about no-member and similar 217 | # checks whenever an opaque object is returned when inferring. The inference 218 | # can return multiple potential results while evaluating a Python object, but 219 | # some branches might not be evaluated, which results in partial inference. In 220 | # that case, it might be useful to still emit no-member and other checks for 221 | # the rest of the inferred objects. 222 | ignore-on-opaque-inference=yes 223 | 224 | # List of class names for which member attributes should not be checked (useful 225 | # for classes with dynamically set attributes). This supports the use of 226 | # qualified names. 227 | ignored-classes=optparse.Values,thread._local,_thread._local 228 | 229 | # List of module names for which member attributes should not be checked 230 | # (useful for modules/projects where namespaces are manipulated during runtime 231 | # and thus existing member attributes cannot be deduced by static analysis). It 232 | # supports qualified module names, as well as Unix pattern matching. 233 | ignored-modules= 234 | 235 | # Show a hint with possible names when a member name was not found. The aspect 236 | # of finding the hint is based on edit distance. 237 | missing-member-hint=yes 238 | 239 | # The minimum edit distance a name should have in order to be considered a 240 | # similar match for a missing member name. 241 | missing-member-hint-distance=1 242 | 243 | # The total number of similar names that should be taken in consideration when 244 | # showing a hint for a missing member. 245 | missing-member-max-choices=1 246 | 247 | # Regex pattern to define which classes are considered mixins ignore-mixin- 248 | # members is set to 'yes' 249 | mixin-class-rgx=.*[Mm]ixin 250 | 251 | # List of decorators that change the signature of a decorated function. 252 | signature-mutators= 253 | 254 | 255 | [FORMAT] 256 | 257 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 258 | expected-line-ending-format= 259 | 260 | # Regexp for a line that is allowed to be longer than the limit. 261 | ignore-long-lines=^\s*(# )??$ 262 | 263 | # Number of spaces of indent required inside a hanging or continued line. 264 | indent-after-paren=4 265 | 266 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 267 | # tab). 268 | indent-string=' ' 269 | 270 | # Maximum number of characters on a single line. 271 | max-line-length=100 272 | 273 | # Maximum number of lines in a module. 274 | max-module-lines=1000 275 | 276 | # Allow the body of a class to be on the same line as the declaration if body 277 | # contains single statement. 278 | single-line-class-stmt=no 279 | 280 | # Allow the body of an if to be on the same line as the test if there is no 281 | # else. 282 | single-line-if-stmt=no 283 | 284 | 285 | [STRING] 286 | 287 | # This flag controls whether inconsistent-quotes generates a warning when the 288 | # character used as a quote delimiter is used inconsistently within a module. 289 | check-quote-consistency=no 290 | 291 | # This flag controls whether the implicit-str-concat should generate a warning 292 | # on implicit string concatenation in sequences defined over several lines. 293 | check-str-concat-over-line-jumps=no 294 | 295 | 296 | [SPELLING] 297 | 298 | # Limits count of emitted suggestions for spelling mistakes. 299 | max-spelling-suggestions=4 300 | 301 | # Spelling dictionary name. Available dictionaries: none. To make it work, 302 | # install the 'python-enchant' package. 303 | spelling-dict= 304 | 305 | # List of comma separated words that should be considered directives if they 306 | # appear and the beginning of a comment and should not be checked. 307 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 308 | 309 | # List of comma separated words that should not be checked. 310 | spelling-ignore-words= 311 | 312 | # A path to a file that contains the private dictionary; one word per line. 313 | spelling-private-dict-file= 314 | 315 | # Tells whether to store unknown words to the private dictionary (see the 316 | # --spelling-private-dict-file option) instead of raising a message. 317 | spelling-store-unknown-words=no 318 | 319 | 320 | [BASIC] 321 | 322 | # Naming style matching correct argument names. 323 | argument-naming-style=snake_case 324 | 325 | # Regular expression matching correct argument names. Overrides argument- 326 | # naming-style. 327 | #argument-rgx= 328 | 329 | # Naming style matching correct attribute names. 330 | attr-naming-style=snake_case 331 | 332 | # Regular expression matching correct attribute names. Overrides attr-naming- 333 | # style. 334 | #attr-rgx= 335 | 336 | # Bad variable names which should always be refused, separated by a comma. 337 | bad-names=foo, 338 | bar, 339 | baz, 340 | toto, 341 | tutu, 342 | tata 343 | 344 | # Bad variable names regexes, separated by a comma. If names match any regex, 345 | # they will always be refused 346 | bad-names-rgxs= 347 | 348 | # Naming style matching correct class attribute names. 349 | class-attribute-naming-style=any 350 | 351 | # Regular expression matching correct class attribute names. Overrides class- 352 | # attribute-naming-style. 353 | #class-attribute-rgx= 354 | 355 | # Naming style matching correct class constant names. 356 | class-const-naming-style=UPPER_CASE 357 | 358 | # Regular expression matching correct class constant names. Overrides class- 359 | # const-naming-style. 360 | #class-const-rgx= 361 | 362 | # Naming style matching correct class names. 363 | class-naming-style=PascalCase 364 | 365 | # Regular expression matching correct class names. Overrides class-naming- 366 | # style. 367 | #class-rgx= 368 | 369 | # Naming style matching correct constant names. 370 | const-naming-style=UPPER_CASE 371 | 372 | # Regular expression matching correct constant names. Overrides const-naming- 373 | # style. 374 | #const-rgx= 375 | 376 | # Minimum line length for functions/classes that require docstrings, shorter 377 | # ones are exempt. 378 | docstring-min-length=-1 379 | 380 | # Naming style matching correct function names. 381 | function-naming-style=snake_case 382 | 383 | # Regular expression matching correct function names. Overrides function- 384 | # naming-style. 385 | #function-rgx= 386 | 387 | # Good variable names which should always be accepted, separated by a comma. 388 | good-names=i, 389 | j, 390 | k, 391 | ex, 392 | Run, 393 | _ 394 | 395 | # Good variable names regexes, separated by a comma. If names match any regex, 396 | # they will always be accepted 397 | good-names-rgxs= 398 | 399 | # Include a hint for the correct naming format with invalid-name. 400 | include-naming-hint=no 401 | 402 | # Naming style matching correct inline iteration names. 403 | inlinevar-naming-style=any 404 | 405 | # Regular expression matching correct inline iteration names. Overrides 406 | # inlinevar-naming-style. 407 | #inlinevar-rgx= 408 | 409 | # Naming style matching correct method names. 410 | method-naming-style=snake_case 411 | 412 | # Regular expression matching correct method names. Overrides method-naming- 413 | # style. 414 | #method-rgx= 415 | 416 | # Naming style matching correct module names. 417 | module-naming-style=snake_case 418 | 419 | # Regular expression matching correct module names. Overrides module-naming- 420 | # style. 421 | #module-rgx= 422 | 423 | # Colon-delimited sets of names that determine each other's naming style when 424 | # the name regexes allow several styles. 425 | name-group= 426 | 427 | # Regular expression which should only match function or class names that do 428 | # not require a docstring. 429 | no-docstring-rgx=^_ 430 | 431 | # List of decorators that produce properties, such as abc.abstractproperty. Add 432 | # to this list to register other decorators that produce valid properties. 433 | # These decorators are taken in consideration only for invalid-name. 434 | property-classes=abc.abstractproperty 435 | 436 | # Naming style matching correct variable names. 437 | variable-naming-style=snake_case 438 | 439 | # Regular expression matching correct variable names. Overrides variable- 440 | # naming-style. 441 | #variable-rgx= 442 | 443 | 444 | [MISCELLANEOUS] 445 | 446 | # List of note tags to take in consideration, separated by a comma. 447 | notes=FIXME, 448 | XXX, 449 | TODO 450 | 451 | # Regular expression of note tags to take in consideration. 452 | #notes-rgx= 453 | 454 | 455 | [DESIGN] 456 | 457 | # List of regular expressions of class ancestor names to ignore when counting 458 | # public methods (see R0903) 459 | exclude-too-few-public-methods= 460 | 461 | # List of qualified class names to ignore when counting class parents (see 462 | # R0901) 463 | ignored-parents= 464 | 465 | # Maximum number of arguments for function / method. 466 | max-args=5 467 | 468 | # Maximum number of attributes for a class (see R0902). 469 | max-attributes=7 470 | 471 | # Maximum number of boolean expressions in an if statement (see R0916). 472 | max-bool-expr=5 473 | 474 | # Maximum number of branch for function / method body. 475 | max-branches=12 476 | 477 | # Maximum number of locals for function / method body. 478 | max-locals=15 479 | 480 | # Maximum number of parents for a class (see R0901). 481 | max-parents=7 482 | 483 | # Maximum number of public methods for a class (see R0904). 484 | max-public-methods=20 485 | 486 | # Maximum number of return / yield for function / method body. 487 | max-returns=6 488 | 489 | # Maximum number of statements in function / method body. 490 | max-statements=50 491 | 492 | # Minimum number of public methods for a class (see R0903). 493 | min-public-methods=2 494 | 495 | 496 | [IMPORTS] 497 | 498 | # List of modules that can be imported at any level, not just the top level 499 | # one. 500 | allow-any-import-level= 501 | 502 | # Allow wildcard imports from modules that define __all__. 503 | allow-wildcard-with-all=no 504 | 505 | # Analyse import fallback blocks. This can be used to support both Python 2 and 506 | # 3 compatible code, which means that the block might have code that exists 507 | # only in one or another interpreter, leading to false positives when analysed. 508 | analyse-fallback-blocks=no 509 | 510 | # Deprecated modules which should not be used, separated by a comma. 511 | deprecated-modules= 512 | 513 | # Output a graph (.gv or any supported image format) of external dependencies 514 | # to the given file (report RP0402 must not be disabled). 515 | ext-import-graph= 516 | 517 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 518 | # external) dependencies to the given file (report RP0402 must not be 519 | # disabled). 520 | import-graph= 521 | 522 | # Output a graph (.gv or any supported image format) of internal dependencies 523 | # to the given file (report RP0402 must not be disabled). 524 | int-import-graph= 525 | 526 | # Force import order to recognize a module as part of the standard 527 | # compatibility libraries. 528 | known-standard-library= 529 | 530 | # Force import order to recognize a module as part of a third party library. 531 | known-third-party=enchant 532 | 533 | # Couples of modules and preferred modules, separated by a comma. 534 | preferred-modules= 535 | 536 | 537 | [CLASSES] 538 | 539 | # Warn about protected attribute access inside special methods 540 | check-protected-access-in-special-methods=no 541 | 542 | # List of method names used to declare (i.e. assign) instance attributes. 543 | defining-attr-methods=__init__, 544 | __new__, 545 | setUp, 546 | __post_init__ 547 | 548 | # List of member names, which should be excluded from the protected access 549 | # warning. 550 | exclude-protected=_asdict, 551 | _fields, 552 | _replace, 553 | _source, 554 | _make 555 | 556 | # List of valid names for the first argument in a class method. 557 | valid-classmethod-first-arg=cls 558 | 559 | # List of valid names for the first argument in a metaclass class method. 560 | valid-metaclass-classmethod-first-arg=cls 561 | 562 | 563 | [EXCEPTIONS] 564 | 565 | # Exceptions that will emit a warning when being caught. Defaults to 566 | # "BaseException, Exception". 567 | overgeneral-exceptions=BaseException, 568 | Exception 569 | -------------------------------------------------------------------------------- /python_wireguard/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__* 2 | bin/*.so 3 | 4 | .env 5 | -------------------------------------------------------------------------------- /python_wireguard/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module is a wrapper for interacting with Wireguard on Linux. 3 | ''' 4 | from .wireguard import list_devices, delete_device 5 | from .key import Key 6 | from .client import Client 7 | from .server_connection import ServerConnection 8 | from .server import Server 9 | from .client_connection import ClientConnection 10 | -------------------------------------------------------------------------------- /python_wireguard/bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarnoaxel/python-wireguard/a28d0f64a8697e7f1b2194678d81eae1c5543a0b/python_wireguard/bin/.gitkeep -------------------------------------------------------------------------------- /python_wireguard/client.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Contains a class representing a client-side Wireguard connection. 3 | ''' 4 | from .wireguard import valid_interface, create_client, delete_device, client_add_peer, enable_device 5 | from .key import Key 6 | from .server_connection import ServerConnection 7 | 8 | class Client: 9 | ''' 10 | This is a client-side Wireguard connection. 11 | ''' 12 | def __init__(self, interface_name, key, local_ip): 13 | if not valid_interface(interface_name): 14 | raise ValueError(f"Invalid interface name {interface_name}") 15 | if not isinstance(key, Key): 16 | raise ValueError("Key should be an instance of python_wireguard.Key") 17 | 18 | self.key = key 19 | self.local_ip = local_ip 20 | self.interface_name = interface_name 21 | self.connection = None 22 | self.interface_created = False 23 | 24 | def create_interface(self): 25 | ''' 26 | Create the internet interface belonging to this client. 27 | ''' 28 | create_client(self.interface_name, self.key.as_bytes(), self.local_ip) 29 | self.interface_created = True 30 | 31 | def delete_interface(self): 32 | ''' 33 | Delete the internet interface belonging to this client. 34 | ''' 35 | delete_device(self.interface_name) 36 | self.interface_created = False 37 | 38 | def set_server(self, server_connection): 39 | ''' 40 | Set the server for this Client. 41 | ''' 42 | if not isinstance(server_connection, ServerConnection): 43 | raise ValueError( 44 | "server_connection should be an instance of python_wireguard.ServerConnection" 45 | ) 46 | self.connection = server_connection 47 | 48 | def connect(self): 49 | ''' 50 | Connect to the Wireguard server. 51 | ''' 52 | if not self.interface_created: 53 | self.create_interface() 54 | if self.connection is None: 55 | raise ValueError( 56 | "The connection has not been configured for this Client yet. \ 57 | Use 'set_server' to set it." 58 | ) 59 | server_connection = self.connection 60 | key = server_connection.get_key() 61 | client_add_peer(self.interface_name, key.as_bytes(), 62 | server_connection.get_endpoint(), server_connection.get_port()) 63 | enable_device(self.interface_name) 64 | -------------------------------------------------------------------------------- /python_wireguard/client_connection.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Contains a class that represents a server-side peer, i.e. a connection to a client. 3 | ''' 4 | class ClientConnection: 5 | ''' 6 | A connection to a Wireguard client. Contains the public key of the client, 7 | and the local IP of the client. 8 | ''' 9 | def __init__(self, public_key, local_ip): 10 | self.public_key = public_key 11 | self.local_ip = local_ip 12 | 13 | def get_key(self): 14 | ''' 15 | Get the public key of the client this points to. 16 | ''' 17 | return self.public_key 18 | 19 | def get_ip(self): 20 | ''' 21 | Get the local IP. 22 | ''' 23 | return self.local_ip 24 | -------------------------------------------------------------------------------- /python_wireguard/key.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class for using a Wireguard public or private key. 3 | ''' 4 | import python_wireguard.wireguard as wg 5 | 6 | class Key: 7 | ''' 8 | This represents a Wireguard key. 9 | ''' 10 | def __init__(self, base64_string=None): 11 | if base64_string is None: 12 | self.byte_content = wg.empty_key() 13 | elif len(base64_string) != 44: 14 | raise ValueError("Invalid base64 string provided") 15 | else: 16 | self.byte_content = wg.key_from_base64(base64_string) 17 | 18 | def __str__(self): 19 | ''' 20 | Convert this key to a string. 21 | ''' 22 | return wg.key_to_base64(self.byte_content) 23 | 24 | def __repr__(self): 25 | ''' 26 | Represent this class (used in the python cli). 27 | ''' 28 | return f"" 29 | 30 | @staticmethod 31 | def key_pair(): 32 | ''' 33 | Create a new private/public key pair. 34 | ''' 35 | private, public = wg.key_pair() 36 | private_obj = Key(wg.key_to_base64(private)) 37 | public_obj = Key(wg.key_to_base64(public)) 38 | return private_obj, public_obj 39 | 40 | def as_bytes(self): 41 | ''' 42 | Get the c_char_array content of this key. 43 | ''' 44 | return self.byte_content 45 | -------------------------------------------------------------------------------- /python_wireguard/server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains a class representing a server-side Wireguard connection. 3 | """ 4 | from .wireguard import valid_interface, create_server, delete_device, server_add_peer, enable_device 5 | from .key import Key 6 | from .client_connection import ClientConnection 7 | 8 | 9 | class Server: 10 | """ 11 | This is a server-side Wireguard connection. 12 | """ 13 | 14 | def __init__(self, interface_name, key, local_ip, port): 15 | if not valid_interface(interface_name): 16 | raise ValueError(f"Invalid interface name {interface_name}") 17 | if not isinstance(key, Key): 18 | raise ValueError("Key should be an instance of python_wireguard.Key") 19 | self.interface_name = interface_name 20 | self.key = key 21 | self.local_ip = local_ip 22 | self.port = port 23 | self.clients = [] 24 | self.interface_created = False 25 | 26 | def create_interface(self): 27 | """ 28 | Create the network interface belonging to this wg server. 29 | """ 30 | create_server(self.interface_name, self.port, self.key.as_bytes(), self.local_ip) 31 | self.interface_created = True 32 | 33 | def delete_interface(self): 34 | """ 35 | Deletes the network interface of this server. 36 | """ 37 | delete_device(self.interface_name) 38 | self.interface_created = False 39 | 40 | def add_client(self, client_connection): 41 | """ 42 | Add a new client to this server. 43 | """ 44 | if not isinstance(client_connection, ClientConnection): 45 | raise ValueError( 46 | "server_connection should be an instance of python_wireguard.ServerConnection" 47 | ) 48 | server_add_peer(self.interface_name, 49 | client_connection.get_key().as_bytes(), 50 | client_connection.get_ip()) 51 | 52 | def enable(self): 53 | """ 54 | Turn on the network interface. 55 | """ 56 | if not self.interface_created: 57 | self.create_interface() 58 | enable_device(self.interface_name) 59 | -------------------------------------------------------------------------------- /python_wireguard/server_connection.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Contains a class that represents a client-side peer, i.e. a connection to a server. 3 | ''' 4 | class ServerConnection: 5 | ''' 6 | A connection to a Wireguard server. Contains the public key of the server, 7 | and the endpoint IP and port. 8 | ''' 9 | def __init__(self, public_key, host_ip, port): 10 | self.public_key = public_key 11 | self.host_ip = host_ip 12 | self.port = port 13 | 14 | def get_key(self): 15 | ''' 16 | Get the public key of the server this points to. 17 | ''' 18 | return self.public_key 19 | 20 | def get_endpoint(self): 21 | ''' 22 | Get the destination IP. 23 | ''' 24 | return self.host_ip 25 | 26 | def get_port(self): 27 | ''' 28 | Get the server port. 29 | ''' 30 | return self.port 31 | -------------------------------------------------------------------------------- /python_wireguard/wireguard.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file contains functions for directly interacting with the Wireguard shared objects file. 3 | ''' 4 | from ctypes import CDLL, c_ushort, create_string_buffer 5 | import re 6 | import os 7 | 8 | dirname = os.path.dirname(os.path.realpath(__file__)) 9 | c_library = CDLL(dirname + '/bin/py-wireguard.so') 10 | 11 | def valid_interface(interface): 12 | ''' 13 | Validate the name of a network interface. 14 | ''' 15 | return bool(re.match("^[A-Za-z0-9_-]*$", interface)) 16 | 17 | def empty_key(): 18 | ''' 19 | Create an empty key. Used for generating new keys, you should not need this. 20 | ''' 21 | return create_string_buffer(b'\000' * 32) 22 | 23 | def key_pair(): 24 | ''' 25 | Create a private/public key pair fo ruse with Wireguard. 26 | ''' 27 | private, public = empty_key(), empty_key() 28 | c_library.generate_private_key(private) 29 | c_library.generate_public_key(private, public) 30 | return private, public 31 | 32 | def create_server(name, port, private_key, local_ip): 33 | ''' 34 | Create a server-side Wireguard interface. This is a Wireguard instance that listens on a port 35 | to allow clients to connect. 36 | ''' 37 | if valid_interface(name): 38 | c_library.add_server_device(name.encode(), c_ushort(port), private_key) 39 | os.system(f"ip a add dev {name} {local_ip}") 40 | else: 41 | print(f"invalid device name '{name}'") 42 | 43 | def create_client(name, private_key, local_ip): 44 | ''' 45 | Create a client-side Wireguard interface. This means that it won't be listening on a port. 46 | ''' 47 | if valid_interface(name): 48 | c_library.add_client_device(name.encode(), private_key) 49 | os.system(f"ip a add dev {name} {local_ip}") 50 | else: 51 | print(f"invalid device name '{name}'") 52 | 53 | def client_add_peer(device_name, public_key, address, port): 54 | ''' 55 | Add a 'peer' to a client-side setup. This means that the server will be added for this setup. 56 | ''' 57 | c_library.add_server_peer(device_name.encode(), public_key, address.encode(), c_ushort(port)) 58 | 59 | def server_add_peer(device_name, public_key, local_ip): 60 | ''' 61 | Add a client 62 | ''' 63 | c_library.add_client_peer(device_name.encode(), public_key, local_ip.encode()) 64 | 65 | def delete_device(name): 66 | ''' 67 | Delete interface :name: 68 | ''' 69 | c_library.delete_device(name.encode()) 70 | 71 | def list_devices(): 72 | ''' 73 | Print a list of all Wireguard network devices. 74 | ''' 75 | c_library.list_devices() 76 | 77 | def key_to_base64(key): 78 | ''' 79 | Convert a binary key to base64. 80 | ''' 81 | str_val = create_string_buffer(b'\000' * 44) 82 | c_library.key_to_string(key, str_val) 83 | return str_val.value.decode() 84 | 85 | def key_from_base64(base64): 86 | ''' 87 | Create a binary key from a base64 string. 88 | ''' 89 | key = empty_key() 90 | c_library.key_from_string(base64.encode(), key) 91 | return key 92 | 93 | def enable_device(device): 94 | ''' 95 | Turn on a network device with name :device:. 96 | ''' 97 | if valid_interface(device): 98 | os.system(f"ip link set up {device}") 99 | else: 100 | print(f"invalid device '{device}'") 101 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Used for building the python_wireguard package. 3 | ''' 4 | from pathlib import Path 5 | import os 6 | from setuptools import setup 7 | 8 | this_directory = Path(__file__).parent 9 | long_description = (this_directory / "README.md").read_text() 10 | 11 | version = os.environ.get('RELEASE_VERSION') 12 | 13 | setup(name='python_wireguard', 14 | version=version, 15 | description='A python wrapper for controlling Wireguard', 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | url='https://github.com/jarnoaxel/python-wireguard', 19 | author='jarnoaxel', 20 | license='GPL-3.0-or-later', 21 | packages=['python_wireguard'], 22 | include_package_data=True, 23 | keywords=['vpn', 'wireguard'], 24 | zip_safe=False) 25 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | -------------------------------------------------------------------------------- /src/python_interface.c: -------------------------------------------------------------------------------- 1 | #include "wireguard.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * Generate a private key and store it in result. 9 | * @param result Pointer to the location where the key will be stored. 10 | */ 11 | void generate_private_key(unsigned char *result) 12 | { 13 | wg_generate_private_key(result); 14 | } 15 | 16 | /** 17 | * Generate a public key and store it in result. 18 | * @param private The private key to which the public key should correspond. 19 | * @param result Pointer to the location where the key will be stored. 20 | */ 21 | void generate_public_key(unsigned char *private, unsigned char *result) 22 | { 23 | wg_generate_public_key(result, private); 24 | } 25 | 26 | void key_to_string(wg_key key, char *result) 27 | { 28 | wg_key_to_base64(result, key); 29 | } 30 | 31 | void key_from_string(char *string, wg_key key) 32 | { 33 | wg_key_from_base64(key, string); 34 | } 35 | 36 | void add_server_device(char *device_name, uint16_t port, wg_key private_key) 37 | { 38 | wg_device new_device = { 39 | .flags = WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_LISTEN_PORT, 40 | .listen_port = port, 41 | }; 42 | 43 | snprintf(new_device.name, IFNAMSIZ, "%s", device_name); 44 | memcpy(new_device.private_key, private_key, sizeof(new_device.private_key)); 45 | 46 | if (wg_add_device(new_device.name) < 0) { 47 | perror("Unable to add device"); 48 | exit(1); 49 | } 50 | 51 | if (wg_set_device(&new_device) < 0) { 52 | perror("Unable to set device"); 53 | exit(1); 54 | } 55 | } 56 | 57 | void add_client_device(char *device_name, wg_key private_key) 58 | { 59 | wg_device new_device = { 60 | .flags = WGDEVICE_HAS_PRIVATE_KEY, 61 | }; 62 | 63 | snprintf(new_device.name, IFNAMSIZ, "%s", device_name); 64 | memcpy(new_device.private_key, private_key, sizeof(new_device.private_key)); 65 | 66 | if (wg_add_device(new_device.name) < 0) { 67 | perror("Unable to add device"); 68 | exit(1); 69 | } 70 | 71 | if (wg_set_device(&new_device) < 0) { 72 | perror("Unable to set device"); 73 | exit(1); 74 | } 75 | } 76 | 77 | /** 78 | * Add a peer to device 'device_name'. 79 | * @param device_name 80 | * @param public_key 81 | * @param ip_address 82 | */ 83 | void add_server_peer(char *device_name, unsigned char *public_key, char *ip_address, uint16_t port) 84 | { 85 | struct sockaddr_in dest_addr; 86 | bzero(&dest_addr, sizeof (dest_addr)); 87 | dest_addr.sin_family = AF_INET; 88 | dest_addr.sin_port = htons(port); 89 | inet_pton(AF_INET, ip_address, &dest_addr.sin_addr); 90 | 91 | wg_allowedip allowed_ip; 92 | bzero(&allowed_ip, sizeof (allowed_ip)); 93 | inet_pton(AF_INET, "0.0.0.0", &allowed_ip.ip4); 94 | allowed_ip.family = AF_INET; 95 | allowed_ip.cidr = 0; 96 | 97 | wg_peer new_peer = { 98 | .flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS 99 | }; 100 | 101 | new_peer.endpoint.addr4 = dest_addr; 102 | new_peer.first_allowedip = &allowed_ip; 103 | new_peer.last_allowedip = &allowed_ip; 104 | memcpy(new_peer.public_key, public_key, sizeof(new_peer.public_key)); 105 | wg_device *device; 106 | if(wg_get_device(&device, device_name) < 0) { 107 | perror("Unable to get device"); 108 | exit(1); 109 | } 110 | wg_peer *peer; 111 | if (device->last_peer == NULL) { 112 | device->first_peer = &new_peer; 113 | device->last_peer = &new_peer; 114 | } else { 115 | peer = device->last_peer; 116 | peer->next_peer = &new_peer; 117 | device->last_peer = &new_peer; 118 | } 119 | 120 | wg_set_device(device); 121 | } 122 | 123 | /** 124 | * Add a peer to device 'device_name'. 125 | * @param device_name 126 | * @param public_key 127 | * @param ip_address 128 | */ 129 | void add_client_peer(char *device_name, unsigned char *public_key, char *ip_address) 130 | { 131 | wg_allowedip allowed_ip; 132 | bzero(&allowed_ip, sizeof (allowed_ip)); 133 | inet_pton(AF_INET, ip_address, &allowed_ip.ip4); 134 | allowed_ip.family = AF_INET; 135 | allowed_ip.cidr = 32; 136 | 137 | wg_peer new_peer = { 138 | .flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS 139 | }; 140 | 141 | new_peer.first_allowedip = &allowed_ip; 142 | new_peer.last_allowedip = &allowed_ip; 143 | memcpy(new_peer.public_key, public_key, sizeof(new_peer.public_key)); 144 | wg_device *device; 145 | if(wg_get_device(&device, device_name) < 0) { 146 | perror("Unable to get device"); 147 | exit(1); 148 | } 149 | wg_peer *peer; 150 | if (device->last_peer == NULL) { 151 | device->first_peer = &new_peer; 152 | device->last_peer = &new_peer; 153 | } else { 154 | peer = device->last_peer; 155 | peer->next_peer = &new_peer; 156 | device->last_peer = &new_peer; 157 | } 158 | 159 | wg_set_device(device); 160 | } 161 | 162 | int delete_device(char *device_name) 163 | { 164 | if (wg_del_device(device_name) < 0) { 165 | perror("Unable to delete device"); 166 | return 1; 167 | } 168 | return 0; 169 | } 170 | 171 | void list_devices(void) 172 | { 173 | char *device_names, *device_name; 174 | size_t len; 175 | 176 | device_names = wg_list_device_names(); 177 | if (!device_names) { 178 | perror("Unable to get device names"); 179 | exit(1); 180 | } 181 | wg_for_each_device_name(device_names, device_name, len) { 182 | wg_device *device; 183 | wg_peer *peer; 184 | wg_key_b64_string key; 185 | 186 | if (wg_get_device(&device, device_name) < 0) { 187 | perror("Unable to get device"); 188 | continue; 189 | } 190 | if (device->flags & WGDEVICE_HAS_PUBLIC_KEY) { 191 | wg_key_to_base64(key, device->public_key); 192 | printf("%s has public key %s\n", device_name, key); 193 | } else 194 | printf("%s has no public key\n", device_name); 195 | wg_for_each_peer(device, peer) { 196 | wg_key_to_base64(key, peer->public_key); 197 | printf(" - peer %s\n", key); 198 | } 199 | wg_free_device(device); 200 | } 201 | free(device_names); 202 | } -------------------------------------------------------------------------------- /src/wireguard.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1+ 2 | /* 3 | * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. 4 | * Copyright (C) 2008-2012 Pablo Neira Ayuso . 5 | */ 6 | 7 | #define _GNU_SOURCE 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "wireguard.h" 26 | 27 | /* wireguard.h netlink uapi: */ 28 | 29 | #define WG_GENL_NAME "wireguard" 30 | #define WG_GENL_VERSION 1 31 | 32 | enum wg_cmd { 33 | WG_CMD_GET_DEVICE, 34 | WG_CMD_SET_DEVICE, 35 | __WG_CMD_MAX 36 | }; 37 | 38 | enum wgdevice_flag { 39 | WGDEVICE_F_REPLACE_PEERS = 1U << 0 40 | }; 41 | enum wgdevice_attribute { 42 | WGDEVICE_A_UNSPEC, 43 | WGDEVICE_A_IFINDEX, 44 | WGDEVICE_A_IFNAME, 45 | WGDEVICE_A_PRIVATE_KEY, 46 | WGDEVICE_A_PUBLIC_KEY, 47 | WGDEVICE_A_FLAGS, 48 | WGDEVICE_A_LISTEN_PORT, 49 | WGDEVICE_A_FWMARK, 50 | WGDEVICE_A_PEERS, 51 | __WGDEVICE_A_LAST 52 | }; 53 | 54 | enum wgpeer_flag { 55 | WGPEER_F_REMOVE_ME = 1U << 0, 56 | WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1 57 | }; 58 | enum wgpeer_attribute { 59 | WGPEER_A_UNSPEC, 60 | WGPEER_A_PUBLIC_KEY, 61 | WGPEER_A_PRESHARED_KEY, 62 | WGPEER_A_FLAGS, 63 | WGPEER_A_ENDPOINT, 64 | WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, 65 | WGPEER_A_LAST_HANDSHAKE_TIME, 66 | WGPEER_A_RX_BYTES, 67 | WGPEER_A_TX_BYTES, 68 | WGPEER_A_ALLOWEDIPS, 69 | WGPEER_A_PROTOCOL_VERSION, 70 | __WGPEER_A_LAST 71 | }; 72 | 73 | enum wgallowedip_attribute { 74 | WGALLOWEDIP_A_UNSPEC, 75 | WGALLOWEDIP_A_FAMILY, 76 | WGALLOWEDIP_A_IPADDR, 77 | WGALLOWEDIP_A_CIDR_MASK, 78 | __WGALLOWEDIP_A_LAST 79 | }; 80 | 81 | /* libmnl mini library: */ 82 | 83 | #define MNL_SOCKET_AUTOPID 0 84 | #define MNL_ALIGNTO 4 85 | #define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1)) 86 | #define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr)) 87 | #define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr)) 88 | 89 | enum mnl_attr_data_type { 90 | MNL_TYPE_UNSPEC, 91 | MNL_TYPE_U8, 92 | MNL_TYPE_U16, 93 | MNL_TYPE_U32, 94 | MNL_TYPE_U64, 95 | MNL_TYPE_STRING, 96 | MNL_TYPE_FLAG, 97 | MNL_TYPE_MSECS, 98 | MNL_TYPE_NESTED, 99 | MNL_TYPE_NESTED_COMPAT, 100 | MNL_TYPE_NUL_STRING, 101 | MNL_TYPE_BINARY, 102 | MNL_TYPE_MAX, 103 | }; 104 | 105 | #define mnl_attr_for_each(attr, nlh, offset) \ 106 | for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \ 107 | mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \ 108 | (attr) = mnl_attr_next(attr)) 109 | 110 | #define mnl_attr_for_each_nested(attr, nest) \ 111 | for ((attr) = mnl_attr_get_payload(nest); \ 112 | mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \ 113 | (attr) = mnl_attr_next(attr)) 114 | 115 | #define mnl_attr_for_each_payload(payload, payload_size) \ 116 | for ((attr) = (payload); \ 117 | mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \ 118 | (attr) = mnl_attr_next(attr)) 119 | 120 | #define MNL_CB_ERROR -1 121 | #define MNL_CB_STOP 0 122 | #define MNL_CB_OK 1 123 | 124 | typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data); 125 | typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data); 126 | 127 | #ifndef MNL_ARRAY_SIZE 128 | #define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) 129 | #endif 130 | 131 | static size_t mnl_ideal_socket_buffer_size(void) 132 | { 133 | static size_t size = 0; 134 | 135 | if (size) 136 | return size; 137 | size = (size_t)sysconf(_SC_PAGESIZE); 138 | if (size > 8192) 139 | size = 8192; 140 | return size; 141 | } 142 | 143 | static size_t mnl_nlmsg_size(size_t len) 144 | { 145 | return len + MNL_NLMSG_HDRLEN; 146 | } 147 | 148 | static struct nlmsghdr *mnl_nlmsg_put_header(void *buf) 149 | { 150 | int len = MNL_ALIGN(sizeof(struct nlmsghdr)); 151 | struct nlmsghdr *nlh = buf; 152 | 153 | memset(buf, 0, len); 154 | nlh->nlmsg_len = len; 155 | return nlh; 156 | } 157 | 158 | static void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size) 159 | { 160 | char *ptr = (char *)nlh + nlh->nlmsg_len; 161 | size_t len = MNL_ALIGN(size); 162 | nlh->nlmsg_len += len; 163 | memset(ptr, 0, len); 164 | return ptr; 165 | } 166 | 167 | static void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh) 168 | { 169 | return (void *)nlh + MNL_NLMSG_HDRLEN; 170 | } 171 | 172 | static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset) 173 | { 174 | return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset); 175 | } 176 | 177 | static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len) 178 | { 179 | return len >= (int)sizeof(struct nlmsghdr) && 180 | nlh->nlmsg_len >= sizeof(struct nlmsghdr) && 181 | (int)nlh->nlmsg_len <= len; 182 | } 183 | 184 | static struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len) 185 | { 186 | *len -= MNL_ALIGN(nlh->nlmsg_len); 187 | return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len)); 188 | } 189 | 190 | static void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh) 191 | { 192 | return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len); 193 | } 194 | 195 | static bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq) 196 | { 197 | return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true; 198 | } 199 | 200 | static bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid) 201 | { 202 | return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true; 203 | } 204 | 205 | static uint16_t mnl_attr_get_type(const struct nlattr *attr) 206 | { 207 | return attr->nla_type & NLA_TYPE_MASK; 208 | } 209 | 210 | static uint16_t mnl_attr_get_payload_len(const struct nlattr *attr) 211 | { 212 | return attr->nla_len - MNL_ATTR_HDRLEN; 213 | } 214 | 215 | static void *mnl_attr_get_payload(const struct nlattr *attr) 216 | { 217 | return (void *)attr + MNL_ATTR_HDRLEN; 218 | } 219 | 220 | static bool mnl_attr_ok(const struct nlattr *attr, int len) 221 | { 222 | return len >= (int)sizeof(struct nlattr) && 223 | attr->nla_len >= sizeof(struct nlattr) && 224 | (int)attr->nla_len <= len; 225 | } 226 | 227 | static struct nlattr *mnl_attr_next(const struct nlattr *attr) 228 | { 229 | return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len)); 230 | } 231 | 232 | static int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max) 233 | { 234 | if (mnl_attr_get_type(attr) > max) { 235 | errno = EOPNOTSUPP; 236 | return -1; 237 | } 238 | return 1; 239 | } 240 | 241 | static int __mnl_attr_validate(const struct nlattr *attr, 242 | enum mnl_attr_data_type type, size_t exp_len) 243 | { 244 | uint16_t attr_len = mnl_attr_get_payload_len(attr); 245 | const char *attr_data = mnl_attr_get_payload(attr); 246 | 247 | if (attr_len < exp_len) { 248 | errno = ERANGE; 249 | return -1; 250 | } 251 | switch(type) { 252 | case MNL_TYPE_FLAG: 253 | if (attr_len > 0) { 254 | errno = ERANGE; 255 | return -1; 256 | } 257 | break; 258 | case MNL_TYPE_NUL_STRING: 259 | if (attr_len == 0) { 260 | errno = ERANGE; 261 | return -1; 262 | } 263 | if (attr_data[attr_len-1] != '\0') { 264 | errno = EINVAL; 265 | return -1; 266 | } 267 | break; 268 | case MNL_TYPE_STRING: 269 | if (attr_len == 0) { 270 | errno = ERANGE; 271 | return -1; 272 | } 273 | break; 274 | case MNL_TYPE_NESTED: 275 | 276 | if (attr_len == 0) 277 | break; 278 | 279 | if (attr_len < MNL_ATTR_HDRLEN) { 280 | errno = ERANGE; 281 | return -1; 282 | } 283 | break; 284 | default: 285 | 286 | break; 287 | } 288 | if (exp_len && attr_len > exp_len) { 289 | errno = ERANGE; 290 | return -1; 291 | } 292 | return 0; 293 | } 294 | 295 | static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = { 296 | [MNL_TYPE_U8] = sizeof(uint8_t), 297 | [MNL_TYPE_U16] = sizeof(uint16_t), 298 | [MNL_TYPE_U32] = sizeof(uint32_t), 299 | [MNL_TYPE_U64] = sizeof(uint64_t), 300 | [MNL_TYPE_MSECS] = sizeof(uint64_t), 301 | }; 302 | 303 | static int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type) 304 | { 305 | int exp_len; 306 | 307 | if (type >= MNL_TYPE_MAX) { 308 | errno = EINVAL; 309 | return -1; 310 | } 311 | exp_len = mnl_attr_data_type_len[type]; 312 | return __mnl_attr_validate(attr, type, exp_len); 313 | } 314 | 315 | static int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset, 316 | mnl_attr_cb_t cb, void *data) 317 | { 318 | int ret = MNL_CB_OK; 319 | const struct nlattr *attr; 320 | 321 | mnl_attr_for_each(attr, nlh, offset) 322 | if ((ret = cb(attr, data)) <= MNL_CB_STOP) 323 | return ret; 324 | return ret; 325 | } 326 | 327 | static int mnl_attr_parse_nested(const struct nlattr *nested, mnl_attr_cb_t cb, 328 | void *data) 329 | { 330 | int ret = MNL_CB_OK; 331 | const struct nlattr *attr; 332 | 333 | mnl_attr_for_each_nested(attr, nested) 334 | if ((ret = cb(attr, data)) <= MNL_CB_STOP) 335 | return ret; 336 | return ret; 337 | } 338 | 339 | static uint8_t mnl_attr_get_u8(const struct nlattr *attr) 340 | { 341 | return *((uint8_t *)mnl_attr_get_payload(attr)); 342 | } 343 | 344 | static uint16_t mnl_attr_get_u16(const struct nlattr *attr) 345 | { 346 | return *((uint16_t *)mnl_attr_get_payload(attr)); 347 | } 348 | 349 | static uint32_t mnl_attr_get_u32(const struct nlattr *attr) 350 | { 351 | return *((uint32_t *)mnl_attr_get_payload(attr)); 352 | } 353 | 354 | static uint64_t mnl_attr_get_u64(const struct nlattr *attr) 355 | { 356 | uint64_t tmp; 357 | memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp)); 358 | return tmp; 359 | } 360 | 361 | static const char *mnl_attr_get_str(const struct nlattr *attr) 362 | { 363 | return mnl_attr_get_payload(attr); 364 | } 365 | 366 | static void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len, 367 | const void *data) 368 | { 369 | struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh); 370 | uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len; 371 | int pad; 372 | 373 | attr->nla_type = type; 374 | attr->nla_len = payload_len; 375 | memcpy(mnl_attr_get_payload(attr), data, len); 376 | nlh->nlmsg_len += MNL_ALIGN(payload_len); 377 | pad = MNL_ALIGN(len) - len; 378 | if (pad > 0) 379 | memset(mnl_attr_get_payload(attr) + len, 0, pad); 380 | } 381 | 382 | static void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data) 383 | { 384 | mnl_attr_put(nlh, type, sizeof(uint16_t), &data); 385 | } 386 | 387 | static void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data) 388 | { 389 | mnl_attr_put(nlh, type, sizeof(uint32_t), &data); 390 | } 391 | 392 | static void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data) 393 | { 394 | mnl_attr_put(nlh, type, strlen(data)+1, data); 395 | } 396 | 397 | static struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type) 398 | { 399 | struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh); 400 | 401 | start->nla_type = NLA_F_NESTED | type; 402 | nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr)); 403 | return start; 404 | } 405 | 406 | static bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen, 407 | uint16_t type, size_t len, const void *data) 408 | { 409 | if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen) 410 | return false; 411 | mnl_attr_put(nlh, type, len, data); 412 | return true; 413 | } 414 | 415 | static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen, 416 | uint16_t type, uint8_t data) 417 | { 418 | return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data); 419 | } 420 | 421 | static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen, 422 | uint16_t type, uint16_t data) 423 | { 424 | return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data); 425 | } 426 | 427 | static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen, 428 | uint16_t type, uint32_t data) 429 | { 430 | return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data); 431 | } 432 | 433 | static struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen, 434 | uint16_t type) 435 | { 436 | if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen) 437 | return NULL; 438 | return mnl_attr_nest_start(nlh, type); 439 | } 440 | 441 | static void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start) 442 | { 443 | start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start; 444 | } 445 | 446 | static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start) 447 | { 448 | nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start; 449 | } 450 | 451 | static int mnl_cb_noop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data) 452 | { 453 | return MNL_CB_OK; 454 | } 455 | 456 | static int mnl_cb_error(const struct nlmsghdr *nlh, __attribute__((unused)) void *data) 457 | { 458 | const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); 459 | 460 | if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) { 461 | errno = EBADMSG; 462 | return MNL_CB_ERROR; 463 | } 464 | 465 | if (err->error < 0) 466 | errno = -err->error; 467 | else 468 | errno = err->error; 469 | 470 | return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; 471 | } 472 | 473 | static int mnl_cb_stop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data) 474 | { 475 | return MNL_CB_STOP; 476 | } 477 | 478 | static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = { 479 | [NLMSG_NOOP] = mnl_cb_noop, 480 | [NLMSG_ERROR] = mnl_cb_error, 481 | [NLMSG_DONE] = mnl_cb_stop, 482 | [NLMSG_OVERRUN] = mnl_cb_noop, 483 | }; 484 | 485 | static int __mnl_cb_run(const void *buf, size_t numbytes, 486 | unsigned int seq, unsigned int portid, 487 | mnl_cb_t cb_data, void *data, 488 | const mnl_cb_t *cb_ctl_array, 489 | unsigned int cb_ctl_array_len) 490 | { 491 | int ret = MNL_CB_OK, len = numbytes; 492 | const struct nlmsghdr *nlh = buf; 493 | 494 | while (mnl_nlmsg_ok(nlh, len)) { 495 | 496 | if (!mnl_nlmsg_portid_ok(nlh, portid)) { 497 | errno = ESRCH; 498 | return -1; 499 | } 500 | 501 | if (!mnl_nlmsg_seq_ok(nlh, seq)) { 502 | errno = EPROTO; 503 | return -1; 504 | } 505 | 506 | if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) { 507 | errno = EINTR; 508 | return -1; 509 | } 510 | 511 | if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) { 512 | if (cb_data){ 513 | ret = cb_data(nlh, data); 514 | if (ret <= MNL_CB_STOP) 515 | goto out; 516 | } 517 | } else if (nlh->nlmsg_type < cb_ctl_array_len) { 518 | if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) { 519 | ret = cb_ctl_array[nlh->nlmsg_type](nlh, data); 520 | if (ret <= MNL_CB_STOP) 521 | goto out; 522 | } 523 | } else if (default_cb_array[nlh->nlmsg_type]) { 524 | ret = default_cb_array[nlh->nlmsg_type](nlh, data); 525 | if (ret <= MNL_CB_STOP) 526 | goto out; 527 | } 528 | nlh = mnl_nlmsg_next(nlh, &len); 529 | } 530 | out: 531 | return ret; 532 | } 533 | 534 | static int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq, 535 | unsigned int portid, mnl_cb_t cb_data, void *data, 536 | const mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len) 537 | { 538 | return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, 539 | cb_ctl_array, cb_ctl_array_len); 540 | } 541 | 542 | static int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, 543 | unsigned int portid, mnl_cb_t cb_data, void *data) 544 | { 545 | return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0); 546 | } 547 | 548 | struct mnl_socket { 549 | int fd; 550 | struct sockaddr_nl addr; 551 | }; 552 | 553 | static unsigned int mnl_socket_get_portid(const struct mnl_socket *nl) 554 | { 555 | return nl->addr.nl_pid; 556 | } 557 | 558 | static struct mnl_socket *__mnl_socket_open(int bus, int flags) 559 | { 560 | struct mnl_socket *nl; 561 | 562 | nl = calloc(1, sizeof(struct mnl_socket)); 563 | if (nl == NULL) 564 | return NULL; 565 | 566 | nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus); 567 | if (nl->fd == -1) { 568 | free(nl); 569 | return NULL; 570 | } 571 | 572 | return nl; 573 | } 574 | 575 | static struct mnl_socket *mnl_socket_open(int bus) 576 | { 577 | return __mnl_socket_open(bus, 0); 578 | } 579 | 580 | static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid) 581 | { 582 | int ret; 583 | socklen_t addr_len; 584 | 585 | nl->addr.nl_family = AF_NETLINK; 586 | nl->addr.nl_groups = groups; 587 | nl->addr.nl_pid = pid; 588 | 589 | ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr)); 590 | if (ret < 0) 591 | return ret; 592 | 593 | addr_len = sizeof(nl->addr); 594 | ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len); 595 | if (ret < 0) 596 | return ret; 597 | 598 | if (addr_len != sizeof(nl->addr)) { 599 | errno = EINVAL; 600 | return -1; 601 | } 602 | if (nl->addr.nl_family != AF_NETLINK) { 603 | errno = EINVAL; 604 | return -1; 605 | } 606 | return 0; 607 | } 608 | 609 | static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf, 610 | size_t len) 611 | { 612 | static const struct sockaddr_nl snl = { 613 | .nl_family = AF_NETLINK 614 | }; 615 | return sendto(nl->fd, buf, len, 0, 616 | (struct sockaddr *) &snl, sizeof(snl)); 617 | } 618 | 619 | static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf, 620 | size_t bufsiz) 621 | { 622 | ssize_t ret; 623 | struct sockaddr_nl addr; 624 | struct iovec iov = { 625 | .iov_base = buf, 626 | .iov_len = bufsiz, 627 | }; 628 | struct msghdr msg = { 629 | .msg_name = &addr, 630 | .msg_namelen = sizeof(struct sockaddr_nl), 631 | .msg_iov = &iov, 632 | .msg_iovlen = 1, 633 | .msg_control = NULL, 634 | .msg_controllen = 0, 635 | .msg_flags = 0, 636 | }; 637 | ret = recvmsg(nl->fd, &msg, 0); 638 | if (ret == -1) 639 | return ret; 640 | 641 | if (msg.msg_flags & MSG_TRUNC) { 642 | errno = ENOSPC; 643 | return -1; 644 | } 645 | if (msg.msg_namelen != sizeof(struct sockaddr_nl)) { 646 | errno = EINVAL; 647 | return -1; 648 | } 649 | return ret; 650 | } 651 | 652 | static int mnl_socket_close(struct mnl_socket *nl) 653 | { 654 | int ret = close(nl->fd); 655 | free(nl); 656 | return ret; 657 | } 658 | 659 | /* mnlg mini library: */ 660 | 661 | struct mnlg_socket { 662 | struct mnl_socket *nl; 663 | char *buf; 664 | uint16_t id; 665 | uint8_t version; 666 | unsigned int seq; 667 | unsigned int portid; 668 | }; 669 | 670 | static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, 671 | uint16_t flags, uint16_t id, 672 | uint8_t version) 673 | { 674 | struct nlmsghdr *nlh; 675 | struct genlmsghdr *genl; 676 | 677 | nlh = mnl_nlmsg_put_header(nlg->buf); 678 | nlh->nlmsg_type = id; 679 | nlh->nlmsg_flags = flags; 680 | nlg->seq = time(NULL); 681 | nlh->nlmsg_seq = nlg->seq; 682 | 683 | genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); 684 | genl->cmd = cmd; 685 | genl->version = version; 686 | 687 | return nlh; 688 | } 689 | 690 | static struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, 691 | uint16_t flags) 692 | { 693 | return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version); 694 | } 695 | 696 | static int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh) 697 | { 698 | return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len); 699 | } 700 | 701 | static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data) 702 | { 703 | (void)nlh; 704 | (void)data; 705 | return MNL_CB_OK; 706 | } 707 | 708 | static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data) 709 | { 710 | const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); 711 | (void)data; 712 | 713 | if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) { 714 | errno = EBADMSG; 715 | return MNL_CB_ERROR; 716 | } 717 | /* Netlink subsystems returns the errno value with different signess */ 718 | if (err->error < 0) 719 | errno = -err->error; 720 | else 721 | errno = err->error; 722 | 723 | return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; 724 | } 725 | 726 | static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data) 727 | { 728 | (void)data; 729 | if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) { 730 | int error = *(int *)mnl_nlmsg_get_payload(nlh); 731 | /* Netlink subsystems returns the errno value with different signess */ 732 | if (error < 0) 733 | errno = -error; 734 | else 735 | errno = error; 736 | 737 | return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; 738 | } 739 | return MNL_CB_STOP; 740 | } 741 | 742 | static const mnl_cb_t mnlg_cb_array[] = { 743 | [NLMSG_NOOP] = mnlg_cb_noop, 744 | [NLMSG_ERROR] = mnlg_cb_error, 745 | [NLMSG_DONE] = mnlg_cb_stop, 746 | [NLMSG_OVERRUN] = mnlg_cb_noop, 747 | }; 748 | 749 | static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data) 750 | { 751 | int err; 752 | 753 | do { 754 | err = mnl_socket_recvfrom(nlg->nl, nlg->buf, 755 | mnl_ideal_socket_buffer_size()); 756 | if (err <= 0) 757 | break; 758 | err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid, 759 | data_cb, data, mnlg_cb_array, MNL_ARRAY_SIZE(mnlg_cb_array)); 760 | } while (err > 0); 761 | 762 | return err; 763 | } 764 | 765 | static int get_family_id_attr_cb(const struct nlattr *attr, void *data) 766 | { 767 | const struct nlattr **tb = data; 768 | int type = mnl_attr_get_type(attr); 769 | 770 | if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) 771 | return MNL_CB_ERROR; 772 | 773 | if (type == CTRL_ATTR_FAMILY_ID && 774 | mnl_attr_validate(attr, MNL_TYPE_U16) < 0) 775 | return MNL_CB_ERROR; 776 | tb[type] = attr; 777 | return MNL_CB_OK; 778 | } 779 | 780 | static int get_family_id_cb(const struct nlmsghdr *nlh, void *data) 781 | { 782 | uint16_t *p_id = data; 783 | struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 }; 784 | 785 | mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb); 786 | if (!tb[CTRL_ATTR_FAMILY_ID]) 787 | return MNL_CB_ERROR; 788 | *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); 789 | return MNL_CB_OK; 790 | } 791 | 792 | static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version) 793 | { 794 | struct mnlg_socket *nlg; 795 | struct nlmsghdr *nlh; 796 | int err; 797 | 798 | nlg = malloc(sizeof(*nlg)); 799 | if (!nlg) 800 | return NULL; 801 | nlg->id = 0; 802 | 803 | err = -ENOMEM; 804 | nlg->buf = malloc(mnl_ideal_socket_buffer_size()); 805 | if (!nlg->buf) 806 | goto err_buf_alloc; 807 | 808 | nlg->nl = mnl_socket_open(NETLINK_GENERIC); 809 | if (!nlg->nl) { 810 | err = -errno; 811 | goto err_mnl_socket_open; 812 | } 813 | 814 | if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) { 815 | err = -errno; 816 | goto err_mnl_socket_bind; 817 | } 818 | 819 | nlg->portid = mnl_socket_get_portid(nlg->nl); 820 | 821 | nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY, 822 | NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1); 823 | mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name); 824 | 825 | if (mnlg_socket_send(nlg, nlh) < 0) { 826 | err = -errno; 827 | goto err_mnlg_socket_send; 828 | } 829 | 830 | errno = 0; 831 | if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) { 832 | errno = errno == ENOENT ? EPROTONOSUPPORT : errno; 833 | err = errno ? -errno : -ENOSYS; 834 | goto err_mnlg_socket_recv_run; 835 | } 836 | 837 | nlg->version = version; 838 | errno = 0; 839 | return nlg; 840 | 841 | err_mnlg_socket_recv_run: 842 | err_mnlg_socket_send: 843 | err_mnl_socket_bind: 844 | mnl_socket_close(nlg->nl); 845 | err_mnl_socket_open: 846 | free(nlg->buf); 847 | err_buf_alloc: 848 | free(nlg); 849 | errno = -err; 850 | return NULL; 851 | } 852 | 853 | static void mnlg_socket_close(struct mnlg_socket *nlg) 854 | { 855 | mnl_socket_close(nlg->nl); 856 | free(nlg->buf); 857 | free(nlg); 858 | } 859 | 860 | /* wireguard-specific parts: */ 861 | 862 | struct string_list { 863 | char *buffer; 864 | size_t len; 865 | size_t cap; 866 | }; 867 | 868 | static int string_list_add(struct string_list *list, const char *str) 869 | { 870 | size_t len = strlen(str) + 1; 871 | 872 | if (len == 1) 873 | return 0; 874 | 875 | if (len >= list->cap - list->len) { 876 | char *new_buffer; 877 | size_t new_cap = list->cap * 2; 878 | 879 | if (new_cap < list->len +len + 1) 880 | new_cap = list->len + len + 1; 881 | new_buffer = realloc(list->buffer, new_cap); 882 | if (!new_buffer) 883 | return -errno; 884 | list->buffer = new_buffer; 885 | list->cap = new_cap; 886 | } 887 | memcpy(list->buffer + list->len, str, len); 888 | list->len += len; 889 | list->buffer[list->len] = '\0'; 890 | return 0; 891 | } 892 | 893 | struct interface { 894 | const char *name; 895 | bool is_wireguard; 896 | }; 897 | 898 | static int parse_linkinfo(const struct nlattr *attr, void *data) 899 | { 900 | struct interface *interface = data; 901 | 902 | if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr))) 903 | interface->is_wireguard = true; 904 | return MNL_CB_OK; 905 | } 906 | 907 | static int parse_infomsg(const struct nlattr *attr, void *data) 908 | { 909 | struct interface *interface = data; 910 | 911 | if (mnl_attr_get_type(attr) == IFLA_LINKINFO) 912 | return mnl_attr_parse_nested(attr, parse_linkinfo, data); 913 | else if (mnl_attr_get_type(attr) == IFLA_IFNAME) 914 | interface->name = mnl_attr_get_str(attr); 915 | return MNL_CB_OK; 916 | } 917 | 918 | static int read_devices_cb(const struct nlmsghdr *nlh, void *data) 919 | { 920 | struct string_list *list = data; 921 | struct interface interface = { 0 }; 922 | int ret; 923 | 924 | ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface); 925 | if (ret != MNL_CB_OK) 926 | return ret; 927 | if (interface.name && interface.is_wireguard) 928 | ret = string_list_add(list, interface.name); 929 | if (ret < 0) 930 | return ret; 931 | if (nlh->nlmsg_type != NLMSG_DONE) 932 | return MNL_CB_OK + 1; 933 | return MNL_CB_OK; 934 | } 935 | 936 | static int fetch_device_names(struct string_list *list) 937 | { 938 | struct mnl_socket *nl = NULL; 939 | char *rtnl_buffer = NULL; 940 | size_t message_len; 941 | unsigned int portid, seq; 942 | ssize_t len; 943 | int ret = 0; 944 | struct nlmsghdr *nlh; 945 | struct ifinfomsg *ifm; 946 | 947 | ret = -ENOMEM; 948 | rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1); 949 | if (!rtnl_buffer) 950 | goto cleanup; 951 | 952 | nl = mnl_socket_open(NETLINK_ROUTE); 953 | if (!nl) { 954 | ret = -errno; 955 | goto cleanup; 956 | } 957 | 958 | if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { 959 | ret = -errno; 960 | goto cleanup; 961 | } 962 | 963 | seq = time(NULL); 964 | portid = mnl_socket_get_portid(nl); 965 | nlh = mnl_nlmsg_put_header(rtnl_buffer); 966 | nlh->nlmsg_type = RTM_GETLINK; 967 | nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP; 968 | nlh->nlmsg_seq = seq; 969 | ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); 970 | ifm->ifi_family = AF_UNSPEC; 971 | message_len = nlh->nlmsg_len; 972 | 973 | if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) { 974 | ret = -errno; 975 | goto cleanup; 976 | } 977 | 978 | another: 979 | if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) { 980 | ret = -errno; 981 | goto cleanup; 982 | } 983 | if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < 0) { 984 | /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed 985 | * during the dump. That's unfortunate, but is pretty common on busy 986 | * systems that are adding and removing tunnels all the time. Rather 987 | * than retrying, potentially indefinitely, we just work with the 988 | * partial results. */ 989 | if (errno != EINTR) { 990 | ret = -errno; 991 | goto cleanup; 992 | } 993 | } 994 | if (len == MNL_CB_OK + 1) 995 | goto another; 996 | ret = 0; 997 | 998 | cleanup: 999 | free(rtnl_buffer); 1000 | if (nl) 1001 | mnl_socket_close(nl); 1002 | return ret; 1003 | } 1004 | 1005 | static int add_del_iface(const char *ifname, bool add) 1006 | { 1007 | struct mnl_socket *nl = NULL; 1008 | char *rtnl_buffer; 1009 | ssize_t len; 1010 | int ret; 1011 | struct nlmsghdr *nlh; 1012 | struct ifinfomsg *ifm; 1013 | struct nlattr *nest; 1014 | 1015 | rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1); 1016 | if (!rtnl_buffer) { 1017 | ret = -ENOMEM; 1018 | goto cleanup; 1019 | } 1020 | 1021 | nl = mnl_socket_open(NETLINK_ROUTE); 1022 | if (!nl) { 1023 | ret = -errno; 1024 | goto cleanup; 1025 | } 1026 | 1027 | if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { 1028 | ret = -errno; 1029 | goto cleanup; 1030 | } 1031 | 1032 | nlh = mnl_nlmsg_put_header(rtnl_buffer); 1033 | nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK; 1034 | nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0); 1035 | nlh->nlmsg_seq = time(NULL); 1036 | ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); 1037 | ifm->ifi_family = AF_UNSPEC; 1038 | mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname); 1039 | nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO); 1040 | mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME); 1041 | mnl_attr_nest_end(nlh, nest); 1042 | 1043 | if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) { 1044 | ret = -errno; 1045 | goto cleanup; 1046 | } 1047 | if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) { 1048 | ret = -errno; 1049 | goto cleanup; 1050 | } 1051 | if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) { 1052 | ret = -errno; 1053 | goto cleanup; 1054 | } 1055 | ret = 0; 1056 | 1057 | cleanup: 1058 | free(rtnl_buffer); 1059 | if (nl) 1060 | mnl_socket_close(nl); 1061 | return ret; 1062 | } 1063 | 1064 | int wg_set_device(wg_device *dev) 1065 | { 1066 | int ret = 0; 1067 | wg_peer *peer = NULL; 1068 | wg_allowedip *allowedip = NULL; 1069 | struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest; 1070 | struct nlmsghdr *nlh; 1071 | struct mnlg_socket *nlg; 1072 | 1073 | nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); 1074 | if (!nlg) 1075 | return -errno; 1076 | 1077 | again: 1078 | nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK); 1079 | mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name); 1080 | 1081 | if (!peer) { 1082 | uint32_t flags = 0; 1083 | 1084 | if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) 1085 | mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key); 1086 | if (dev->flags & WGDEVICE_HAS_LISTEN_PORT) 1087 | mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port); 1088 | if (dev->flags & WGDEVICE_HAS_FWMARK) 1089 | mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark); 1090 | if (dev->flags & WGDEVICE_REPLACE_PEERS) 1091 | flags |= WGDEVICE_F_REPLACE_PEERS; 1092 | if (flags) 1093 | mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags); 1094 | } 1095 | if (!dev->first_peer) 1096 | goto send; 1097 | peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL; 1098 | peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS); 1099 | for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) { 1100 | uint32_t flags = 0; 1101 | 1102 | peer_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0); 1103 | if (!peer_nest) 1104 | goto toobig_peers; 1105 | if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key)) 1106 | goto toobig_peers; 1107 | if (peer->flags & WGPEER_REMOVE_ME) 1108 | flags |= WGPEER_F_REMOVE_ME; 1109 | if (!allowedip) { 1110 | if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS) 1111 | flags |= WGPEER_F_REPLACE_ALLOWEDIPS; 1112 | if (peer->flags & WGPEER_HAS_PRESHARED_KEY) { 1113 | if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key)) 1114 | goto toobig_peers; 1115 | } 1116 | if (peer->endpoint.addr.sa_family == AF_INET) { 1117 | if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4)) 1118 | goto toobig_peers; 1119 | } else if (peer->endpoint.addr.sa_family == AF_INET6) { 1120 | if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6)) 1121 | goto toobig_peers; 1122 | } 1123 | if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) { 1124 | if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval)) 1125 | goto toobig_peers; 1126 | } 1127 | } 1128 | if (flags) { 1129 | if (!mnl_attr_put_u32_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_FLAGS, flags)) 1130 | goto toobig_peers; 1131 | } 1132 | if (peer->first_allowedip) { 1133 | if (!allowedip) 1134 | allowedip = peer->first_allowedip; 1135 | allowedips_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ALLOWEDIPS); 1136 | if (!allowedips_nest) 1137 | goto toobig_allowedips; 1138 | for (; allowedip; allowedip = allowedip->next_allowedip) { 1139 | allowedip_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0); 1140 | if (!allowedip_nest) 1141 | goto toobig_allowedips; 1142 | if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_FAMILY, allowedip->family)) 1143 | goto toobig_allowedips; 1144 | if (allowedip->family == AF_INET) { 1145 | if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4)) 1146 | goto toobig_allowedips; 1147 | } else if (allowedip->family == AF_INET6) { 1148 | if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6)) 1149 | goto toobig_allowedips; 1150 | } 1151 | if (!mnl_attr_put_u8_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr)) 1152 | goto toobig_allowedips; 1153 | mnl_attr_nest_end(nlh, allowedip_nest); 1154 | allowedip_nest = NULL; 1155 | } 1156 | mnl_attr_nest_end(nlh, allowedips_nest); 1157 | allowedips_nest = NULL; 1158 | } 1159 | 1160 | mnl_attr_nest_end(nlh, peer_nest); 1161 | peer_nest = NULL; 1162 | } 1163 | mnl_attr_nest_end(nlh, peers_nest); 1164 | peers_nest = NULL; 1165 | goto send; 1166 | toobig_allowedips: 1167 | if (allowedip_nest) 1168 | mnl_attr_nest_cancel(nlh, allowedip_nest); 1169 | if (allowedips_nest) 1170 | mnl_attr_nest_end(nlh, allowedips_nest); 1171 | mnl_attr_nest_end(nlh, peer_nest); 1172 | mnl_attr_nest_end(nlh, peers_nest); 1173 | goto send; 1174 | toobig_peers: 1175 | if (peer_nest) 1176 | mnl_attr_nest_cancel(nlh, peer_nest); 1177 | mnl_attr_nest_end(nlh, peers_nest); 1178 | goto send; 1179 | send: 1180 | if (mnlg_socket_send(nlg, nlh) < 0) { 1181 | ret = -errno; 1182 | goto out; 1183 | } 1184 | errno = 0; 1185 | if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) { 1186 | ret = errno ? -errno : -EINVAL; 1187 | goto out; 1188 | } 1189 | if (peer) 1190 | goto again; 1191 | 1192 | out: 1193 | mnlg_socket_close(nlg); 1194 | errno = -ret; 1195 | return ret; 1196 | } 1197 | 1198 | static int parse_allowedip(const struct nlattr *attr, void *data) 1199 | { 1200 | wg_allowedip *allowedip = data; 1201 | 1202 | switch (mnl_attr_get_type(attr)) { 1203 | case WGALLOWEDIP_A_UNSPEC: 1204 | break; 1205 | case WGALLOWEDIP_A_FAMILY: 1206 | if (!mnl_attr_validate(attr, MNL_TYPE_U16)) 1207 | allowedip->family = mnl_attr_get_u16(attr); 1208 | break; 1209 | case WGALLOWEDIP_A_IPADDR: 1210 | if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4)) 1211 | memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4)); 1212 | else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6)) 1213 | memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6)); 1214 | break; 1215 | case WGALLOWEDIP_A_CIDR_MASK: 1216 | if (!mnl_attr_validate(attr, MNL_TYPE_U8)) 1217 | allowedip->cidr = mnl_attr_get_u8(attr); 1218 | break; 1219 | } 1220 | 1221 | return MNL_CB_OK; 1222 | } 1223 | 1224 | static int parse_allowedips(const struct nlattr *attr, void *data) 1225 | { 1226 | wg_peer *peer = data; 1227 | wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip)); 1228 | int ret; 1229 | 1230 | if (!new_allowedip) 1231 | return MNL_CB_ERROR; 1232 | if (!peer->first_allowedip) 1233 | peer->first_allowedip = peer->last_allowedip = new_allowedip; 1234 | else { 1235 | peer->last_allowedip->next_allowedip = new_allowedip; 1236 | peer->last_allowedip = new_allowedip; 1237 | } 1238 | ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip); 1239 | if (!ret) 1240 | return ret; 1241 | if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) { 1242 | errno = EAFNOSUPPORT; 1243 | return MNL_CB_ERROR; 1244 | } 1245 | return MNL_CB_OK; 1246 | } 1247 | 1248 | bool wg_key_is_zero(const wg_key key) 1249 | { 1250 | volatile uint8_t acc = 0; 1251 | unsigned int i; 1252 | 1253 | for (i = 0; i < sizeof(wg_key); ++i) { 1254 | acc |= key[i]; 1255 | __asm__ ("" : "=r" (acc) : "0" (acc)); 1256 | } 1257 | return 1 & ((acc - 1) >> 8); 1258 | } 1259 | 1260 | static int parse_peer(const struct nlattr *attr, void *data) 1261 | { 1262 | wg_peer *peer = data; 1263 | 1264 | switch (mnl_attr_get_type(attr)) { 1265 | case WGPEER_A_UNSPEC: 1266 | break; 1267 | case WGPEER_A_PUBLIC_KEY: 1268 | if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) { 1269 | memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key)); 1270 | peer->flags |= WGPEER_HAS_PUBLIC_KEY; 1271 | } 1272 | break; 1273 | case WGPEER_A_PRESHARED_KEY: 1274 | if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) { 1275 | memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key)); 1276 | if (!wg_key_is_zero(peer->preshared_key)) 1277 | peer->flags |= WGPEER_HAS_PRESHARED_KEY; 1278 | } 1279 | break; 1280 | case WGPEER_A_ENDPOINT: { 1281 | struct sockaddr *addr; 1282 | 1283 | if (mnl_attr_get_payload_len(attr) < sizeof(*addr)) 1284 | break; 1285 | addr = mnl_attr_get_payload(attr); 1286 | if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4)) 1287 | memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4)); 1288 | else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6)) 1289 | memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6)); 1290 | break; 1291 | } 1292 | case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: 1293 | if (!mnl_attr_validate(attr, MNL_TYPE_U16)) 1294 | peer->persistent_keepalive_interval = mnl_attr_get_u16(attr); 1295 | break; 1296 | case WGPEER_A_LAST_HANDSHAKE_TIME: 1297 | if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time)) 1298 | memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time)); 1299 | break; 1300 | case WGPEER_A_RX_BYTES: 1301 | if (!mnl_attr_validate(attr, MNL_TYPE_U64)) 1302 | peer->rx_bytes = mnl_attr_get_u64(attr); 1303 | break; 1304 | case WGPEER_A_TX_BYTES: 1305 | if (!mnl_attr_validate(attr, MNL_TYPE_U64)) 1306 | peer->tx_bytes = mnl_attr_get_u64(attr); 1307 | break; 1308 | case WGPEER_A_ALLOWEDIPS: 1309 | return mnl_attr_parse_nested(attr, parse_allowedips, peer); 1310 | } 1311 | 1312 | return MNL_CB_OK; 1313 | } 1314 | 1315 | static int parse_peers(const struct nlattr *attr, void *data) 1316 | { 1317 | wg_device *device = data; 1318 | wg_peer *new_peer = calloc(1, sizeof(wg_peer)); 1319 | int ret; 1320 | 1321 | if (!new_peer) 1322 | return MNL_CB_ERROR; 1323 | if (!device->first_peer) 1324 | device->first_peer = device->last_peer = new_peer; 1325 | else { 1326 | device->last_peer->next_peer = new_peer; 1327 | device->last_peer = new_peer; 1328 | } 1329 | ret = mnl_attr_parse_nested(attr, parse_peer, new_peer); 1330 | if (!ret) 1331 | return ret; 1332 | if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) { 1333 | errno = ENXIO; 1334 | return MNL_CB_ERROR; 1335 | } 1336 | return MNL_CB_OK; 1337 | } 1338 | 1339 | static int parse_device(const struct nlattr *attr, void *data) 1340 | { 1341 | wg_device *device = data; 1342 | 1343 | switch (mnl_attr_get_type(attr)) { 1344 | case WGDEVICE_A_UNSPEC: 1345 | break; 1346 | case WGDEVICE_A_IFINDEX: 1347 | if (!mnl_attr_validate(attr, MNL_TYPE_U32)) 1348 | device->ifindex = mnl_attr_get_u32(attr); 1349 | break; 1350 | case WGDEVICE_A_IFNAME: 1351 | if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) { 1352 | strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1); 1353 | device->name[sizeof(device->name) - 1] = '\0'; 1354 | } 1355 | break; 1356 | case WGDEVICE_A_PRIVATE_KEY: 1357 | if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) { 1358 | memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key)); 1359 | device->flags |= WGDEVICE_HAS_PRIVATE_KEY; 1360 | } 1361 | break; 1362 | case WGDEVICE_A_PUBLIC_KEY: 1363 | if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) { 1364 | memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key)); 1365 | device->flags |= WGDEVICE_HAS_PUBLIC_KEY; 1366 | } 1367 | break; 1368 | case WGDEVICE_A_LISTEN_PORT: 1369 | if (!mnl_attr_validate(attr, MNL_TYPE_U16)) 1370 | device->listen_port = mnl_attr_get_u16(attr); 1371 | break; 1372 | case WGDEVICE_A_FWMARK: 1373 | if (!mnl_attr_validate(attr, MNL_TYPE_U32)) 1374 | device->fwmark = mnl_attr_get_u32(attr); 1375 | break; 1376 | case WGDEVICE_A_PEERS: 1377 | return mnl_attr_parse_nested(attr, parse_peers, device); 1378 | } 1379 | 1380 | return MNL_CB_OK; 1381 | } 1382 | 1383 | static int read_device_cb(const struct nlmsghdr *nlh, void *data) 1384 | { 1385 | return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data); 1386 | } 1387 | 1388 | static void coalesce_peers(wg_device *device) 1389 | { 1390 | wg_peer *old_next_peer, *peer = device->first_peer; 1391 | 1392 | while (peer && peer->next_peer) { 1393 | if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) { 1394 | peer = peer->next_peer; 1395 | continue; 1396 | } 1397 | if (!peer->first_allowedip) { 1398 | peer->first_allowedip = peer->next_peer->first_allowedip; 1399 | peer->last_allowedip = peer->next_peer->last_allowedip; 1400 | } else { 1401 | peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip; 1402 | peer->last_allowedip = peer->next_peer->last_allowedip; 1403 | } 1404 | old_next_peer = peer->next_peer; 1405 | peer->next_peer = old_next_peer->next_peer; 1406 | free(old_next_peer); 1407 | } 1408 | } 1409 | 1410 | int wg_get_device(wg_device **device, const char *device_name) 1411 | { 1412 | int ret = 0; 1413 | struct nlmsghdr *nlh; 1414 | struct mnlg_socket *nlg; 1415 | 1416 | try_again: 1417 | *device = calloc(1, sizeof(wg_device)); 1418 | if (!*device) 1419 | return -errno; 1420 | 1421 | nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); 1422 | if (!nlg) { 1423 | wg_free_device(*device); 1424 | *device = NULL; 1425 | return -errno; 1426 | } 1427 | 1428 | nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); 1429 | mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name); 1430 | if (mnlg_socket_send(nlg, nlh) < 0) { 1431 | ret = -errno; 1432 | goto out; 1433 | } 1434 | errno = 0; 1435 | if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) { 1436 | ret = errno ? -errno : -EINVAL; 1437 | goto out; 1438 | } 1439 | coalesce_peers(*device); 1440 | 1441 | out: 1442 | if (nlg) 1443 | mnlg_socket_close(nlg); 1444 | if (ret) { 1445 | wg_free_device(*device); 1446 | if (ret == -EINTR) 1447 | goto try_again; 1448 | *device = NULL; 1449 | } 1450 | errno = -ret; 1451 | return ret; 1452 | } 1453 | 1454 | /* first\0second\0third\0forth\0last\0\0 */ 1455 | char *wg_list_device_names(void) 1456 | { 1457 | struct string_list list = { 0 }; 1458 | int ret = fetch_device_names(&list); 1459 | 1460 | errno = -ret; 1461 | if (errno) { 1462 | free(list.buffer); 1463 | return NULL; 1464 | } 1465 | return list.buffer ?: strdup("\0"); 1466 | } 1467 | 1468 | int wg_add_device(const char *device_name) 1469 | { 1470 | return add_del_iface(device_name, true); 1471 | } 1472 | 1473 | int wg_del_device(const char *device_name) 1474 | { 1475 | return add_del_iface(device_name, false); 1476 | } 1477 | 1478 | void wg_free_device(wg_device *dev) 1479 | { 1480 | wg_peer *peer, *np; 1481 | wg_allowedip *allowedip, *na; 1482 | 1483 | if (!dev) 1484 | return; 1485 | for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) { 1486 | for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL) 1487 | free(allowedip); 1488 | free(peer); 1489 | } 1490 | free(dev); 1491 | } 1492 | 1493 | static void encode_base64(char dest[static 4], const uint8_t src[static 3]) 1494 | { 1495 | const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 }; 1496 | unsigned int i; 1497 | 1498 | for (i = 0; i < 4; ++i) 1499 | dest[i] = input[i] + 'A' 1500 | + (((25 - input[i]) >> 8) & 6) 1501 | - (((51 - input[i]) >> 8) & 75) 1502 | - (((61 - input[i]) >> 8) & 15) 1503 | + (((62 - input[i]) >> 8) & 3); 1504 | 1505 | } 1506 | 1507 | void wg_key_to_base64(wg_key_b64_string base64, const wg_key key) 1508 | { 1509 | unsigned int i; 1510 | 1511 | for (i = 0; i < 32 / 3; ++i) 1512 | encode_base64(&base64[i * 4], &key[i * 3]); 1513 | encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 }); 1514 | base64[sizeof(wg_key_b64_string) - 2] = '='; 1515 | base64[sizeof(wg_key_b64_string) - 1] = '\0'; 1516 | } 1517 | 1518 | static int decode_base64(const char src[static 4]) 1519 | { 1520 | int val = 0; 1521 | unsigned int i; 1522 | 1523 | for (i = 0; i < 4; ++i) 1524 | val |= (-1 1525 | + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64)) 1526 | + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70)) 1527 | + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) 1528 | + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) 1529 | + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64) 1530 | ) << (18 - 6 * i); 1531 | return val; 1532 | } 1533 | 1534 | int wg_key_from_base64(wg_key key, const wg_key_b64_string base64) 1535 | { 1536 | unsigned int i; 1537 | int val; 1538 | volatile uint8_t ret = 0; 1539 | 1540 | if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') { 1541 | errno = EINVAL; 1542 | goto out; 1543 | } 1544 | 1545 | for (i = 0; i < 32 / 3; ++i) { 1546 | val = decode_base64(&base64[i * 4]); 1547 | ret |= (uint32_t)val >> 31; 1548 | key[i * 3 + 0] = (val >> 16) & 0xff; 1549 | key[i * 3 + 1] = (val >> 8) & 0xff; 1550 | key[i * 3 + 2] = val & 0xff; 1551 | } 1552 | val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' }); 1553 | ret |= ((uint32_t)val >> 31) | (val & 0xff); 1554 | key[i * 3 + 0] = (val >> 16) & 0xff; 1555 | key[i * 3 + 1] = (val >> 8) & 0xff; 1556 | errno = EINVAL & ~((ret - 1) >> 8); 1557 | out: 1558 | return -errno; 1559 | } 1560 | 1561 | typedef int64_t fe[16]; 1562 | 1563 | static __attribute__((noinline)) void memzero_explicit(void *s, size_t count) 1564 | { 1565 | memset(s, 0, count); 1566 | __asm__ __volatile__("": :"r"(s) :"memory"); 1567 | } 1568 | 1569 | static void carry(fe o) 1570 | { 1571 | int i; 1572 | 1573 | for (i = 0; i < 16; ++i) { 1574 | o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16); 1575 | o[i] &= 0xffff; 1576 | } 1577 | } 1578 | 1579 | static void cswap(fe p, fe q, int b) 1580 | { 1581 | int i; 1582 | int64_t t, c = ~(b - 1); 1583 | 1584 | for (i = 0; i < 16; ++i) { 1585 | t = c & (p[i] ^ q[i]); 1586 | p[i] ^= t; 1587 | q[i] ^= t; 1588 | } 1589 | 1590 | memzero_explicit(&t, sizeof(t)); 1591 | memzero_explicit(&c, sizeof(c)); 1592 | memzero_explicit(&b, sizeof(b)); 1593 | } 1594 | 1595 | static void pack(uint8_t *o, const fe n) 1596 | { 1597 | int i, j, b; 1598 | fe m, t; 1599 | 1600 | memcpy(t, n, sizeof(t)); 1601 | carry(t); 1602 | carry(t); 1603 | carry(t); 1604 | for (j = 0; j < 2; ++j) { 1605 | m[0] = t[0] - 0xffed; 1606 | for (i = 1; i < 15; ++i) { 1607 | m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); 1608 | m[i - 1] &= 0xffff; 1609 | } 1610 | m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); 1611 | b = (m[15] >> 16) & 1; 1612 | m[14] &= 0xffff; 1613 | cswap(t, m, 1 - b); 1614 | } 1615 | for (i = 0; i < 16; ++i) { 1616 | o[2 * i] = t[i] & 0xff; 1617 | o[2 * i + 1] = t[i] >> 8; 1618 | } 1619 | 1620 | memzero_explicit(m, sizeof(m)); 1621 | memzero_explicit(t, sizeof(t)); 1622 | memzero_explicit(&b, sizeof(b)); 1623 | } 1624 | 1625 | static void add(fe o, const fe a, const fe b) 1626 | { 1627 | int i; 1628 | 1629 | for (i = 0; i < 16; ++i) 1630 | o[i] = a[i] + b[i]; 1631 | } 1632 | 1633 | static void subtract(fe o, const fe a, const fe b) 1634 | { 1635 | int i; 1636 | 1637 | for (i = 0; i < 16; ++i) 1638 | o[i] = a[i] - b[i]; 1639 | } 1640 | 1641 | static void multmod(fe o, const fe a, const fe b) 1642 | { 1643 | int i, j; 1644 | int64_t t[31] = { 0 }; 1645 | 1646 | for (i = 0; i < 16; ++i) { 1647 | for (j = 0; j < 16; ++j) 1648 | t[i + j] += a[i] * b[j]; 1649 | } 1650 | for (i = 0; i < 15; ++i) 1651 | t[i] += 38 * t[i + 16]; 1652 | memcpy(o, t, sizeof(fe)); 1653 | carry(o); 1654 | carry(o); 1655 | 1656 | memzero_explicit(t, sizeof(t)); 1657 | } 1658 | 1659 | static void invert(fe o, const fe i) 1660 | { 1661 | fe c; 1662 | int a; 1663 | 1664 | memcpy(c, i, sizeof(c)); 1665 | for (a = 253; a >= 0; --a) { 1666 | multmod(c, c, c); 1667 | if (a != 2 && a != 4) 1668 | multmod(c, c, i); 1669 | } 1670 | memcpy(o, c, sizeof(fe)); 1671 | 1672 | memzero_explicit(c, sizeof(c)); 1673 | } 1674 | 1675 | static void clamp_key(uint8_t *z) 1676 | { 1677 | z[31] = (z[31] & 127) | 64; 1678 | z[0] &= 248; 1679 | } 1680 | 1681 | void wg_generate_public_key(wg_key public_key, const wg_key private_key) 1682 | { 1683 | int i, r; 1684 | uint8_t z[32]; 1685 | fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f; 1686 | 1687 | memcpy(z, private_key, sizeof(z)); 1688 | clamp_key(z); 1689 | 1690 | for (i = 254; i >= 0; --i) { 1691 | r = (z[i >> 3] >> (i & 7)) & 1; 1692 | cswap(a, b, r); 1693 | cswap(c, d, r); 1694 | add(e, a, c); 1695 | subtract(a, a, c); 1696 | add(c, b, d); 1697 | subtract(b, b, d); 1698 | multmod(d, e, e); 1699 | multmod(f, a, a); 1700 | multmod(a, c, a); 1701 | multmod(c, b, e); 1702 | add(e, a, c); 1703 | subtract(a, a, c); 1704 | multmod(b, a, a); 1705 | subtract(c, d, f); 1706 | multmod(a, c, (const fe){ 0xdb41, 1 }); 1707 | add(a, a, d); 1708 | multmod(c, c, a); 1709 | multmod(a, d, f); 1710 | multmod(d, b, (const fe){ 9 }); 1711 | multmod(b, e, e); 1712 | cswap(a, b, r); 1713 | cswap(c, d, r); 1714 | } 1715 | invert(c, c); 1716 | multmod(a, a, c); 1717 | pack(public_key, a); 1718 | 1719 | memzero_explicit(&r, sizeof(r)); 1720 | memzero_explicit(z, sizeof(z)); 1721 | memzero_explicit(a, sizeof(a)); 1722 | memzero_explicit(b, sizeof(b)); 1723 | memzero_explicit(c, sizeof(c)); 1724 | memzero_explicit(d, sizeof(d)); 1725 | memzero_explicit(e, sizeof(e)); 1726 | memzero_explicit(f, sizeof(f)); 1727 | } 1728 | 1729 | void wg_generate_private_key(wg_key private_key) 1730 | { 1731 | wg_generate_preshared_key(private_key); 1732 | clamp_key(private_key); 1733 | } 1734 | 1735 | void wg_generate_preshared_key(wg_key preshared_key) 1736 | { 1737 | ssize_t ret; 1738 | size_t i; 1739 | int fd; 1740 | #if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) 1741 | if (!getentropy(preshared_key, sizeof(wg_key))) 1742 | return; 1743 | #endif 1744 | #if defined(__NR_getrandom) && defined(__linux__) 1745 | if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key)) 1746 | return; 1747 | #endif 1748 | fd = open("/dev/urandom", O_RDONLY); 1749 | assert(fd >= 0); 1750 | for (i = 0; i < sizeof(wg_key); i += ret) { 1751 | ret = read(fd, preshared_key + i, sizeof(wg_key) - i); 1752 | assert(ret > 0); 1753 | } 1754 | close(fd); 1755 | } 1756 | -------------------------------------------------------------------------------- /src/wireguard.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ 2 | /* 3 | * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. 4 | */ 5 | 6 | #ifndef WIREGUARD_H 7 | #define WIREGUARD_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | typedef uint8_t wg_key[32]; 17 | typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1]; 18 | 19 | /* Cross platform __kernel_timespec */ 20 | struct timespec64 { 21 | int64_t tv_sec; 22 | int64_t tv_nsec; 23 | }; 24 | 25 | typedef struct wg_allowedip { 26 | uint16_t family; 27 | union { 28 | struct in_addr ip4; 29 | struct in6_addr ip6; 30 | }; 31 | uint8_t cidr; 32 | struct wg_allowedip *next_allowedip; 33 | } wg_allowedip; 34 | 35 | enum wg_peer_flags { 36 | WGPEER_REMOVE_ME = 1U << 0, 37 | WGPEER_REPLACE_ALLOWEDIPS = 1U << 1, 38 | WGPEER_HAS_PUBLIC_KEY = 1U << 2, 39 | WGPEER_HAS_PRESHARED_KEY = 1U << 3, 40 | WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4 41 | }; 42 | 43 | typedef union wg_endpoint { 44 | struct sockaddr addr; 45 | struct sockaddr_in addr4; 46 | struct sockaddr_in6 addr6; 47 | } wg_endpoint; 48 | 49 | typedef struct wg_peer { 50 | enum wg_peer_flags flags; 51 | 52 | wg_key public_key; 53 | wg_key preshared_key; 54 | 55 | wg_endpoint endpoint; 56 | 57 | struct timespec64 last_handshake_time; 58 | uint64_t rx_bytes, tx_bytes; 59 | uint16_t persistent_keepalive_interval; 60 | 61 | struct wg_allowedip *first_allowedip, *last_allowedip; 62 | struct wg_peer *next_peer; 63 | } wg_peer; 64 | 65 | enum wg_device_flags { 66 | WGDEVICE_REPLACE_PEERS = 1U << 0, 67 | WGDEVICE_HAS_PRIVATE_KEY = 1U << 1, 68 | WGDEVICE_HAS_PUBLIC_KEY = 1U << 2, 69 | WGDEVICE_HAS_LISTEN_PORT = 1U << 3, 70 | WGDEVICE_HAS_FWMARK = 1U << 4 71 | }; 72 | 73 | typedef struct wg_device { 74 | char name[IFNAMSIZ]; 75 | uint32_t ifindex; 76 | 77 | enum wg_device_flags flags; 78 | 79 | wg_key public_key; 80 | wg_key private_key; 81 | 82 | uint32_t fwmark; 83 | uint16_t listen_port; 84 | 85 | struct wg_peer *first_peer, *last_peer; 86 | } wg_device; 87 | 88 | #define wg_for_each_device_name(__names, __name, __len) for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); (__name) += (__len) + 1) 89 | #define wg_for_each_peer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer) 90 | #define wg_for_each_allowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip) 91 | 92 | int wg_set_device(wg_device *dev); 93 | int wg_get_device(wg_device **dev, const char *device_name); 94 | int wg_add_device(const char *device_name); 95 | int wg_del_device(const char *device_name); 96 | void wg_free_device(wg_device *dev); 97 | char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */ 98 | void wg_key_to_base64(wg_key_b64_string base64, const wg_key key); 99 | int wg_key_from_base64(wg_key key, const wg_key_b64_string base64); 100 | bool wg_key_is_zero(const wg_key key); 101 | void wg_generate_public_key(wg_key public_key, const wg_key private_key); 102 | void wg_generate_private_key(wg_key private_key); 103 | void wg_generate_preshared_key(wg_key preshared_key); 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | wurlitzer 3 | pytest-cov 4 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__* 2 | bin/*.so 3 | 4 | .env 5 | -------------------------------------------------------------------------------- /tests/context.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 5 | 6 | import python_wireguard 7 | import python_wireguard.wireguard as wireguard -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from context import python_wireguard as wg 5 | 6 | 7 | def test_create_client(): 8 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 9 | key = wg.Key(base64_key) 10 | client = wg.Client('wg-pytest', key, "10.0.0.1/24") 11 | assert isinstance(client, wg.Client) 12 | 13 | 14 | def test_create_client_invalid_interface(): 15 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 16 | key = wg.Key(base64_key) 17 | with pytest.raises(ValueError): 18 | client = wg.Client('wg pytest', key, "10.0.0.1/24") 19 | 20 | 21 | def test_create_client_invalid_key(): 22 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 23 | with pytest.raises(ValueError): 24 | client = wg.Client('wg-pytest', base64_key, "10.0.0.1/24") 25 | 26 | 27 | def test_create_delete_interface(capfd): 28 | private, public = wg.Key.key_pair() 29 | client = wg.Client('wg-pytest', private, "10.0.0.1/24") 30 | 31 | client.create_interface() 32 | os.system("wg show") 33 | captured = capfd.readouterr() 34 | assert 'wg-pytest' in captured.out 35 | 36 | client.delete_interface() 37 | os.system("wg show") 38 | captured = capfd.readouterr() 39 | assert 'wg-pytest' not in captured.out 40 | 41 | 42 | def test_connect_no_connection(): 43 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 44 | key = wg.Key(base64_key) 45 | client = wg.Client('wg-pytest', key, "10.0.0.1/24") 46 | with pytest.raises(ValueError): 47 | client.connect() 48 | 49 | 50 | def test_set_invalid_connection(): 51 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 52 | key = wg.Key(base64_key) 53 | client = wg.Client('wg-pytest', key, "10.0.0.1/24") 54 | with pytest.raises(ValueError): 55 | client.set_server("blah") 56 | 57 | 58 | def test_set_connection_connect(capfd): 59 | wg.delete_device('wg-pytest') 60 | private, public = wg.Key.key_pair() 61 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 62 | srv_public = wg.Key(base64_key) 63 | client = wg.Client('wg-pytest', private, "10.0.0.2/24") 64 | conn = wg.ServerConnection(srv_public, "127.0.0.1", 12345) 65 | client.set_server(conn) 66 | client.connect() 67 | 68 | os.system("wg show") 69 | captured = capfd.readouterr() 70 | text = captured.out 71 | assert 'wg-pytest' in text 72 | assert base64_key in text 73 | 74 | client.delete_interface() 75 | -------------------------------------------------------------------------------- /tests/test_client_connection.py: -------------------------------------------------------------------------------- 1 | from context import python_wireguard as wg 2 | 3 | 4 | def test_create_instance(): 5 | private, public = wg.Key.key_pair() 6 | conn = wg.ClientConnection(public, "10.0.0.2") 7 | assert isinstance(conn, wg.ClientConnection) 8 | 9 | 10 | def test_get_key(): 11 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 12 | key = wg.Key(base64_key) 13 | conn = wg.ClientConnection(key, "10.0.0.2") 14 | assert str(conn.get_key()) == base64_key 15 | 16 | 17 | def test_get_ip(): 18 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 19 | key = wg.Key(base64_key) 20 | conn = wg.ClientConnection(key, "10.0.0.2") 21 | assert conn.get_ip() == "10.0.0.2" 22 | -------------------------------------------------------------------------------- /tests/test_keys.py: -------------------------------------------------------------------------------- 1 | from context import python_wireguard as wg 2 | import pytest 3 | 4 | 5 | def test_random(): 6 | secret_a, public_a = wg.Key.key_pair() 7 | secret_b, public_b = wg.Key.key_pair() 8 | 9 | assert str(secret_a) != str(secret_b) 10 | assert str(public_a) != str(public_b) 11 | 12 | 13 | def test_secret_public_different(): 14 | secret, public = wg.Key.key_pair() 15 | assert str(secret) != str(public) 16 | 17 | 18 | def test_from_base64(): 19 | secret, public = wg.Key.key_pair() 20 | other_key = wg.Key(str(secret)) 21 | assert str(secret) == str(other_key) 22 | 23 | 24 | def test_invalid_base64(): 25 | with pytest.raises(ValueError): 26 | wg.Key("some random string") 27 | 28 | 29 | def test_valid_base64(): 30 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 31 | key = wg.Key(base64_key) 32 | assert str(key) == base64_key 33 | 34 | 35 | def test_empty_key(): 36 | empty_base64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" 37 | empty_key = wg.Key() 38 | assert str(empty_key) == empty_base64 39 | 40 | 41 | def test_repr(): 42 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 43 | key = wg.Key(base64_key) 44 | assert base64_key in key.__repr__() 45 | 46 | 47 | def test_byte_count(): 48 | private, public = wg.Key.key_pair() 49 | assert len(private.as_bytes()) == 33 50 | -------------------------------------------------------------------------------- /tests/test_server.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from context import python_wireguard as wg 5 | 6 | 7 | def test_create_server(): 8 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 9 | key = wg.Key(base64_key) 10 | server = wg.Server('wg-pytest', key, "10.0.0.1/24", 1234) 11 | assert isinstance(server, wg.Server) 12 | 13 | 14 | def test_create_server_invalid_interface(): 15 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 16 | key = wg.Key(base64_key) 17 | with pytest.raises(ValueError): 18 | server = wg.Server('wg pytest', key, "10.0.0.1/24", 1234) 19 | 20 | 21 | def test_create_server_invalid_key(): 22 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 23 | with pytest.raises(ValueError): 24 | server = wg.Server('wg-pytest', base64_key, "10.0.0.1/24", 1234) 25 | 26 | 27 | def test_create_delete_interface(capfd): 28 | private, public = wg.Key.key_pair() 29 | server = wg.Server('wg-pytest', private, "10.0.0.1/24", 1234) 30 | 31 | server.create_interface() 32 | os.system("wg show") 33 | captured = capfd.readouterr() 34 | assert 'wg-pytest' in captured.out 35 | 36 | server.delete_interface() 37 | os.system("wg show") 38 | captured = capfd.readouterr() 39 | assert 'wg-pytest' not in captured.out 40 | 41 | 42 | def test_add_invalid_connection(): 43 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 44 | key = wg.Key(base64_key) 45 | server = wg.Server('wg-pytest', key, "10.0.0.1/24", 1234) 46 | with pytest.raises(ValueError): 47 | server.add_client("blah") 48 | 49 | 50 | def test_add_valid_connection(capfd): 51 | private, public = wg.Key.key_pair() 52 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 53 | key = wg.Key(base64_key) 54 | server = wg.Server('wg-pytest', private, "10.0.0.1/24", 1234) 55 | server.enable() 56 | conn = wg.ClientConnection(key, "10.0.0.2") 57 | server.add_client(conn) 58 | 59 | os.system("wg show") 60 | captured = capfd.readouterr() 61 | text = captured.out 62 | assert 'wg-pytest' in text 63 | assert base64_key in text 64 | 65 | server.delete_interface() 66 | -------------------------------------------------------------------------------- /tests/test_server_connection.py: -------------------------------------------------------------------------------- 1 | from context import python_wireguard as wg 2 | 3 | 4 | def test_create_instance(): 5 | private, public = wg.Key.key_pair() 6 | conn = wg.ServerConnection(public, "10.0.0.2", 1234) 7 | assert isinstance(conn, wg.ServerConnection) 8 | 9 | 10 | def test_get_key(): 11 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 12 | key = wg.Key(base64_key) 13 | conn = wg.ServerConnection(key, "10.0.0.2", 1234) 14 | assert str(conn.get_key()) == base64_key 15 | 16 | 17 | def test_get_endpoint(): 18 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 19 | key = wg.Key(base64_key) 20 | conn = wg.ServerConnection(key, "10.0.0.2", 1234) 21 | assert conn.get_endpoint() == "10.0.0.2" 22 | 23 | 24 | def test_get_port(): 25 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 26 | key = wg.Key(base64_key) 27 | conn = wg.ServerConnection(key, "10.0.0.2", 1234) 28 | assert conn.get_port() == 1234 29 | -------------------------------------------------------------------------------- /tests/test_wireguard.py: -------------------------------------------------------------------------------- 1 | from context import wireguard 2 | import os 3 | from wurlitzer import pipes 4 | 5 | 6 | def test_valid_interface(): 7 | assert wireguard.valid_interface('wg0') is True 8 | assert wireguard.valid_interface('contains space') is False 9 | 10 | 11 | def test_empty_key(): 12 | key = wireguard.empty_key() 13 | assert len(key) == 33 14 | 15 | 16 | def test_key_pair(): 17 | private, public = wireguard.key_pair() 18 | assert len(private) == len(public) 19 | 20 | 21 | def test_create_server_delete(capfd): 22 | wireguard.delete_device('wg-pytest') 23 | private, public = wireguard.key_pair() 24 | wireguard.create_server('wg-pytest', 4242, private, "10.42.42.1/24") 25 | wireguard.enable_device('wg-pytest') 26 | os.system("wg show") 27 | captured = capfd.readouterr() 28 | assert 'wg-pytest' in captured.out 29 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 30 | other_public = wireguard.key_from_base64(base64_key) 31 | wireguard.server_add_peer('wg-pytest', other_public, "10.0.0.2") 32 | os.system("wg show") 33 | captured = capfd.readouterr() 34 | assert base64_key in captured.out 35 | wireguard.delete_device('wg-pytest') 36 | os.system("wg show") 37 | captured = capfd.readouterr() 38 | assert 'wg-pytest' not in captured.out 39 | 40 | 41 | def test_create_server_invalid_name(capfd): 42 | private, public = wireguard.key_pair() 43 | wireguard.create_server('invalid name', 4242, private, "10.42.42.1/24") 44 | captured = capfd.readouterr() 45 | text = captured.out 46 | assert 'invalid device name' in text 47 | 48 | 49 | def test_create_client_delete(capfd): 50 | wireguard.delete_device('wg-pytest') 51 | private, public = wireguard.key_pair() 52 | base64_key = "YJZ1GXi2cqGj3tMANDnk0D0k18r0x1pByzS4kP8mVEU=" 53 | other_public = wireguard.key_from_base64(base64_key) 54 | wireguard.create_client('wg-pytest', private, "10.42.42.1/24") 55 | os.system("wg show") 56 | captured = capfd.readouterr() 57 | assert 'wg-pytest' in captured.out 58 | with pipes() as (out, err): 59 | wireguard.list_devices() 60 | assert 'wg-pytest' in out.read() 61 | wireguard.client_add_peer('wg-pytest', other_public, "10.1.2.7", 12345) 62 | os.system("wg show") 63 | captured = capfd.readouterr() 64 | assert base64_key in captured.out 65 | wireguard.delete_device('wg-pytest') 66 | os.system("wg show") 67 | captured = capfd.readouterr() 68 | assert 'wg-pytest' not in captured.out 69 | 70 | 71 | def test_create_client_invalid_name(capfd): 72 | private, public = wireguard.key_pair() 73 | wireguard.create_client('invalid name', private, "10.42.42.1/24") 74 | captured = capfd.readouterr() 75 | text = captured.out 76 | assert 'invalid device name' in text 77 | 78 | 79 | def test_enable_device_invalid(capfd): 80 | wireguard.enable_device("invalid name") 81 | captured = capfd.readouterr() 82 | text = captured.out 83 | assert 'invalid device' in text 84 | --------------------------------------------------------------------------------