├── .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 | 
5 | 
6 | 
7 | 
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 |
--------------------------------------------------------------------------------