Initial commit
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
commit
8132305e54
43 changed files with 4379 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
cscope.out
|
||||
*.o
|
||||
*.ko
|
||||
*.mod.c
|
||||
Module.symvers
|
||||
*.cmd
|
||||
.tmp_versions
|
||||
*.swp
|
||||
modules.order
|
||||
modules.builtin
|
339
COPYING
Normal file
339
COPYING
Normal file
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# WireGuard — fast, modern, secure kernel VPN tunnel
|
||||
#### by [Jason A. Donenfeld](mailto:Jason@zx2c4.com) of [Edge Security](http://www.edgesecurity.com/)
|
||||
|
||||
WireGuard is a novel VPN that runs inside the Linux Kernel and utilizes **state-of-the-art [cryptography](doc/protocol.md)**. It aims to be faster, simpler, leaner, and more useful than IPSec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. It runs over UDP.
|
||||
|
||||
**More information may be found at [WireGuard.io](https://www.wireguard.io/).**
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the [GPLv2](COPYING).
|
8
contrib/benchmarking/configs/other.conf
Normal file
8
contrib/benchmarking/configs/other.conf
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Interface]
|
||||
ListenPort = 27183
|
||||
PrivateKey = oHilodMrwJSD1UUIkAkyCek2yqy1Frs5XuN47ShGFk0=
|
||||
|
||||
[Peer]
|
||||
PublicKey = S8hEvD+dam+PrwG4GrSPtE2Pl3ylO/oiUnUDXw3vnx0=
|
||||
AllowedIPs = 192.168.2.2/32
|
||||
Endpoint = 10.10.10.100:38292
|
8
contrib/benchmarking/configs/thinkpad.conf
Normal file
8
contrib/benchmarking/configs/thinkpad.conf
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Interface]
|
||||
ListenPort = 38292
|
||||
PrivateKey = MPCo/WSBkm/DCkbEXUhtjc5u//IeD6wEeaw3Q2HxFGw=
|
||||
|
||||
[Peer]
|
||||
PublicKey = c5PwaIZcVZFDuoDdQJGnYe+fk+wt0qANARpnZDOvqhw=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = 172.16.48.128:27183
|
2
contrib/benchmarking/openvpn-config.txt
Normal file
2
contrib/benchmarking/openvpn-config.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Server: openvpn --dev tun --ifconfig 192.168.3.1 192.168.3.2 --secret static.key --cipher AES-256-CBC --auth SHA256 --port 61721
|
||||
Client: openvpn --dev tun --ifconfig 192.168.3.2 192.168.3.1 --secret static.key --cipher AES-256-CBC --auth SHA256 --port 61721 --remote 10.10.10.1
|
21
contrib/benchmarking/static.key
Normal file
21
contrib/benchmarking/static.key
Normal file
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# 2048 bit OpenVPN static key
|
||||
#
|
||||
-----BEGIN OpenVPN Static key V1-----
|
||||
12abb34ac1cb716576642c7e4c9719af
|
||||
b311929f6bb5a7b9082c9ac3a02dc77a
|
||||
26fc65ba97e67d1dc5b273e72760caba
|
||||
6c8a3321acdf89bfd0469528bfc9ed89
|
||||
1c9c3762d1e18786c8b6dd590456f158
|
||||
d1f625810da1225864c23d7e848ca5d7
|
||||
18a49c4b7e640f8e51001ace9222de75
|
||||
e05177fd01b32d702bd12b45b085678c
|
||||
239e3927d98912174ac648d0e37a3247
|
||||
45cabcbea7cf70832f8800a8b863a35a
|
||||
933c5921fd65882b050bd1096a0c6c60
|
||||
638fb22eafb9f49c13573236d0427441
|
||||
c98869ba8de30e597452237527e7dcc6
|
||||
519058a919de4432203dc1d7622fb4d0
|
||||
f8f20c5350256cdf17bb3b85c5c838fc
|
||||
6ddeb4da9dae8b0b882cb043db483a9d
|
||||
-----END OpenVPN Static key V1-----
|
20
contrib/client-server-example/client.sh
Executable file
20
contrib/client-server-example/client.sh
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
[[ $UID == 0 ]] || { echo "You must be root to run this."; exit 1; }
|
||||
umask 077
|
||||
trap 'rm -f /tmp/wg_private_key' EXIT INT TERM
|
||||
exec 3<>/dev/tcp/demo.wireguard.io/42912
|
||||
wg genkey | tee /tmp/wg_private_key | wg pubkey >&3
|
||||
IFS=: read -r status server_pubkey server_port internal_ip <&3
|
||||
[[ $status == OK ]]
|
||||
ip link del dev wg0 2>/dev/null || true
|
||||
ip link add dev wg0 type wireguard
|
||||
wg set wg0 private-key /tmp/wg_private_key peer "$server_pubkey" allowed-ips 0.0.0.0/0 endpoint "demo.wireguard.io:$server_port"
|
||||
ip address add "$internal_ip"/24 dev wg0
|
||||
ip link set up dev wg0
|
||||
if [ "$1" == "default-route" ]; then
|
||||
host="$(wg show wg0 endpoints | sed -n 's/.*\t\(.*\):.*/\1/p')"
|
||||
ip route add $(ip route get $host | sed '/ via [0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/{s/^\(.* via [0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\).*/\1/}' | head -n 1) 2>/dev/null || true
|
||||
ip route add 0/1 dev wg0
|
||||
ip route add 128/1 dev wg0
|
||||
fi
|
14
contrib/client-server-example/server.sh
Executable file
14
contrib/client-server-example/server.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
if [[ -z $NCAT_REMOTE_ADDR ]]; then
|
||||
ip link del dev wg0 2>/dev/null
|
||||
set -e
|
||||
ip link add dev wg0 type wireguard
|
||||
ip address add 192.168.4.1/24 dev wg0
|
||||
wg set wg0 private-key <(wg genkey) listen-port 12912
|
||||
ip link set up dev wg0
|
||||
exec ncat -e "$(readlink -f "$0")" -k -l -p 42912 -v
|
||||
fi
|
||||
read -r public_key
|
||||
[[ $(wg show wg0 | grep peer | wc -l) -ge 253 ]] && wg set wg0 peer $(wg show wg0 latest-handshakes | sort -k 2 -b -n | head -n 1 | cut -f 1) remove
|
||||
next_ip=$(all="$(wg show wg0 allowed-ips)"; for ((i=2; i<=254; i++)); do ip="192.168.4.$i"; [[ $all != *$ip/32* ]] && echo $ip && break; done)
|
||||
wg set wg0 peer "$public_key" allowed-ips $next_ip/32 2>/dev/null && echo "OK:$(wg show wg0 private-key | wg pubkey):$(wg show wg0 listen-port):$next_ip" || echo ERROR
|
63
contrib/external-tests/go/main.go
Normal file
63
contrib/external-tests/go/main.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/titanous/noise"
|
||||
"net"
|
||||
"time"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"github.com/dchest/blake2s"
|
||||
)
|
||||
|
||||
func assert(exp bool) {
|
||||
if !exp {
|
||||
panic("Assertion failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
my_private, _ := base64.StdEncoding.DecodeString("WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo=")
|
||||
my_public, _ := base64.StdEncoding.DecodeString("K5sF9yESrSBsOXPd6TcpKNgqoy1Ik3ZFKl4FolzrRyI=")
|
||||
preshared, _ := base64.StdEncoding.DecodeString("FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=")
|
||||
their_public, _ := base64.StdEncoding.DecodeString("qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM=")
|
||||
cs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s)
|
||||
hs := noise.NewHandshakeState(noise.Config{CipherSuite: cs, Random: rand.Reader, Pattern: noise.HandshakeIK, Initiator: true, Prologue: []byte("WireGuard v0 zx2c4 Jason@zx2c4.com"), PresharedKey: preshared, StaticKeypair: noise.DHKey{Private: my_private, Public: my_public}, PeerStatic: their_public})
|
||||
conn, _ := net.Dial("udp", "test.wireguard.io:51820")
|
||||
|
||||
now := time.Now()
|
||||
tai64n := make([]byte, 12)
|
||||
binary.BigEndian.PutUint64(tai64n[:], uint64(now.Unix()))
|
||||
binary.BigEndian.PutUint32(tai64n[8:], uint32(now.UnixNano()))
|
||||
initiation_packet := make([]byte, 5)
|
||||
initiation_packet[0] = 1 /* Type: Initiation */
|
||||
binary.LittleEndian.PutUint32(initiation_packet[1:], 28) /* Sender index: 28 (arbitrary) */
|
||||
initiation_packet, _, _ = hs.WriteMessage(initiation_packet, tai64n)
|
||||
hasher, _ := blake2s.New(&blake2s.Config{Size: 16, Key: preshared})
|
||||
hasher.Write(their_public)
|
||||
hasher.Write(initiation_packet)
|
||||
initiation_packet = append(initiation_packet, hasher.Sum(nil)[:16]...)
|
||||
initiation_packet = append(initiation_packet, bytes.Repeat([]byte{ 0 }, 16)...)
|
||||
conn.Write(initiation_packet)
|
||||
|
||||
response_packet := make([]byte, 89)
|
||||
conn.Read(response_packet)
|
||||
assert(response_packet[0] == 2 /* Type: Response */)
|
||||
their_index := binary.LittleEndian.Uint32(response_packet[1:])
|
||||
our_index := binary.LittleEndian.Uint32(response_packet[5:])
|
||||
assert(our_index == 28)
|
||||
payload, send_cs, _, err := hs.ReadMessage(nil, response_packet[9:57])
|
||||
assert(len(payload) == 0 && err == nil)
|
||||
|
||||
keepalive_packet := make([]byte, 13)
|
||||
keepalive_packet[0] = 4 /* Type: Data */
|
||||
binary.LittleEndian.PutUint32(keepalive_packet[1:], their_index)
|
||||
binary.LittleEndian.PutUint64(keepalive_packet[3:], 0) /* Nonce */
|
||||
keepalive_packet = send_cs.Encrypt(keepalive_packet, nil, nil)
|
||||
conn.Write(keepalive_packet)
|
||||
|
||||
conn.Close()
|
||||
}
|
2
contrib/external-tests/haskell/Setup.hs
Normal file
2
contrib/external-tests/haskell/Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Distribution.Simple
|
||||
main = defaultMain
|
34
contrib/external-tests/haskell/cacophony-wg.cabal
Normal file
34
contrib/external-tests/haskell/cacophony-wg.cabal
Normal file
|
@ -0,0 +1,34 @@
|
|||
-- Initial cacophony-wg.cabal generated by cabal init. For further
|
||||
-- documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: cacophony-wg
|
||||
version: 0.1.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
license: PublicDomain
|
||||
license-file: LICENSE
|
||||
author: John Galt
|
||||
maintainer: centromere@users.noreply.github.com
|
||||
-- copyright:
|
||||
-- category:
|
||||
build-type: Simple
|
||||
-- extra-source-files:
|
||||
cabal-version: >=1.10
|
||||
|
||||
executable cacophony-wg
|
||||
main-is: Main.hs
|
||||
other-modules:
|
||||
Data.Time.TAI64
|
||||
build-depends:
|
||||
base >=4.8 && <4.9,
|
||||
base16-bytestring,
|
||||
base64-bytestring,
|
||||
blake2,
|
||||
bytestring,
|
||||
cacophony,
|
||||
cereal,
|
||||
cryptonite,
|
||||
network,
|
||||
time
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
86
contrib/external-tests/haskell/src/Data/Time/TAI64.hs
Normal file
86
contrib/external-tests/haskell/src/Data/Time/TAI64.hs
Normal file
|
@ -0,0 +1,86 @@
|
|||
module Data.Time.TAI64 (
|
||||
TAI64(..)
|
||||
, TAI64N(..)
|
||||
, TAI64NA(..)
|
||||
, posixToTAI64
|
||||
, posixToTAI64N
|
||||
, posixToTAI64NA
|
||||
, getCurrentTAI64
|
||||
, getCurrentTAI64N
|
||||
, getCurrentTAI64NA
|
||||
, tAI64ToPosix
|
||||
, tAI64NToPosix
|
||||
, tAI64NAToPosix
|
||||
) where
|
||||
|
||||
import Data.Serialize
|
||||
import Control.Monad
|
||||
import Data.Word
|
||||
|
||||
import Data.Time.Clock
|
||||
import Data.Time.Clock.POSIX
|
||||
|
||||
import Numeric
|
||||
|
||||
data TAI64 = TAI64
|
||||
{-# UNPACK #-} !Word64
|
||||
deriving (Eq, Ord)
|
||||
|
||||
data TAI64N = TAI64N
|
||||
{-# UNPACK #-} !TAI64
|
||||
{-# UNPACK #-} !Word32
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
data TAI64NA = TAI64NA
|
||||
{-# UNPACK #-} !TAI64N
|
||||
{-# UNPACK #-} !Word32
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
instance Show TAI64 where
|
||||
show (TAI64 t) = "TAI64 0x" ++ showHex t ""
|
||||
|
||||
instance Serialize TAI64 where
|
||||
put (TAI64 t) = putWord64be t
|
||||
get = liftM TAI64 get
|
||||
|
||||
instance Serialize TAI64N where
|
||||
put (TAI64N t' nt) = put t' >> putWord32be nt
|
||||
get = liftM2 TAI64N get get
|
||||
|
||||
instance Serialize TAI64NA where
|
||||
put (TAI64NA t' at) = put t' >> putWord32be at
|
||||
get = liftM2 TAI64NA get get
|
||||
|
||||
|
||||
posixToTAI64 :: POSIXTime -> TAI64
|
||||
posixToTAI64 = TAI64 . (2^62 +) . truncate . realToFrac
|
||||
|
||||
posixToTAI64N :: POSIXTime -> TAI64N
|
||||
posixToTAI64N pt = TAI64N t' ns where
|
||||
t' = posixToTAI64 pt
|
||||
ns = (`mod` 10^9) $ truncate (pts * 10**9)
|
||||
pts = realToFrac pt
|
||||
|
||||
posixToTAI64NA :: POSIXTime -> TAI64NA -- | PICOsecond precision
|
||||
posixToTAI64NA pt = TAI64NA t' as where
|
||||
t' = posixToTAI64N pt
|
||||
as = (`mod` 10^9) $ truncate (pts * 10**18)
|
||||
pts = realToFrac pt
|
||||
|
||||
getCurrentTAI64 :: IO TAI64
|
||||
getCurrentTAI64N :: IO TAI64N
|
||||
getCurrentTAI64NA :: IO TAI64NA
|
||||
getCurrentTAI64 = liftM posixToTAI64 getPOSIXTime
|
||||
getCurrentTAI64N = liftM posixToTAI64N getPOSIXTime
|
||||
getCurrentTAI64NA = liftM posixToTAI64NA getPOSIXTime
|
||||
|
||||
tAI64ToPosix :: TAI64 -> POSIXTime
|
||||
tAI64ToPosix (TAI64 s) = fromRational . fromIntegral $ s - 2^62
|
||||
|
||||
tAI64NToPosix :: TAI64N -> POSIXTime
|
||||
tAI64NToPosix (TAI64N t' n) = tAI64ToPosix t' + nanopart where
|
||||
nanopart = fromRational $ (toRational $ 10**(-9)) * toRational n -- TODO: optimize?
|
||||
|
||||
tAI64NAToPosix :: TAI64NA -> POSIXTime
|
||||
tAI64NAToPosix (TAI64NA t' a) = tAI64NToPosix t' + attopart where
|
||||
attopart = fromRational $ (toRational $ 10**(-18)) * toRational a
|
81
contrib/external-tests/haskell/src/Main.hs
Normal file
81
contrib/external-tests/haskell/src/Main.hs
Normal file
|
@ -0,0 +1,81 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Main where
|
||||
|
||||
import Control.Applicative ((<$>))
|
||||
import Control.Concurrent.MVar
|
||||
import Control.Monad (void)
|
||||
import Data.ByteString.Char8 (pack, unpack, take, drop, replicate)
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString.Base16 as Hex
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.Serialize as S
|
||||
import Prelude hiding (take, drop, replicate)
|
||||
import System.Environment
|
||||
import Network.Socket
|
||||
import qualified Network.Socket.ByteString as NBS
|
||||
|
||||
import Crypto.Hash.BLAKE2.BLAKE2s
|
||||
import Crypto.Noise.Cipher
|
||||
import Crypto.Noise.Cipher.ChaChaPoly1305
|
||||
import Crypto.Noise.Curve
|
||||
import Crypto.Noise.Curve.Curve25519
|
||||
import Crypto.Noise.Handshake
|
||||
import Crypto.Noise.HandshakePatterns
|
||||
import Crypto.Noise.Hash.BLAKE2s
|
||||
import Crypto.Noise.Types
|
||||
|
||||
import Data.Time.TAI64
|
||||
|
||||
w :: PublicKey Curve25519
|
||||
-> Plaintext
|
||||
-> Socket
|
||||
-> SockAddr
|
||||
-> ByteString
|
||||
-> IO ()
|
||||
w theirPub (Plaintext myPSK) sock addr msg = do
|
||||
let x = "\x01\x00\x00" `mappend` msg
|
||||
mac = hash 16 myPSK (sbToBS' (curvePubToBytes theirPub) `mappend` sbToBS' x)
|
||||
void $ NBS.sendTo sock (x `mappend` mac `mappend` replicate 16 '\0') addr
|
||||
|
||||
r :: MVar ByteString -> Socket -> IO ByteString
|
||||
r smv sock = do
|
||||
(r, _) <- NBS.recvFrom sock 1024
|
||||
putMVar smv $ (take 2 . drop 1) r
|
||||
return . take 48 . drop 5 $ r
|
||||
|
||||
payload :: IO Plaintext
|
||||
payload = do
|
||||
tai64n <- getCurrentTAI64N
|
||||
return . Plaintext . bsToSB' $ S.encode tai64n
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let ip = "test.wireguard.io"
|
||||
let port = "51820"
|
||||
let mykey = "WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo="
|
||||
let serverkey = "qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM="
|
||||
let psk = "FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE="
|
||||
addrInfo <- head <$> getAddrInfo Nothing (Just ip) (Just port)
|
||||
sock <- socket (addrFamily addrInfo) Datagram defaultProtocol
|
||||
|
||||
let addr = addrAddress addrInfo
|
||||
mykey' = curveBytesToPair . bsToSB' . either undefined id . B64.decode . pack $ mykey :: KeyPair Curve25519
|
||||
serverkey' = curveBytesToPub . bsToSB' . either undefined id . B64.decode . pack $ serverkey :: PublicKey Curve25519
|
||||
psk' = Plaintext . bsToSB' . either undefined id . B64.decode . pack $ psk
|
||||
hs = handshakeState $ HandshakeStateParams
|
||||
noiseIK
|
||||
"WireGuard v0 zx2c4 Jason@zx2c4.com"
|
||||
(Just psk')
|
||||
(Just mykey')
|
||||
Nothing
|
||||
(Just serverkey')
|
||||
Nothing
|
||||
True :: HandshakeState ChaChaPoly1305 Curve25519 BLAKE2s
|
||||
|
||||
senderindexmv <- newEmptyMVar
|
||||
let hc = HandshakeCallbacks (w serverkey' psk' sock addr) (r senderindexmv sock) (\_ -> return ()) payload
|
||||
(encryption, decryption) <- runHandshake hs hc
|
||||
|
||||
let (keepAlive, encryption') = encryptPayload "" encryption
|
||||
senderindex <- takeMVar senderindexmv
|
||||
void $ NBS.sendTo sock ("\x04" `mappend` senderindex `mappend` replicate 8 '\0' `mappend` keepAlive) addr
|
2
contrib/external-tests/rust/.gitignore
vendored
Normal file
2
contrib/external-tests/rust/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Cargo.lock
|
||||
target/
|
10
contrib/external-tests/rust/Cargo.toml
Normal file
10
contrib/external-tests/rust/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "wireguard-keepalive"
|
||||
version = "0.1.0"
|
||||
authors = ["jason@zx2c4.com"]
|
||||
[dependencies]
|
||||
screech = { git = "https://github.com/trevp/screech" }
|
||||
rust-crypto = "*"
|
||||
byteorder = "*"
|
||||
rustc-serialize = "*"
|
||||
time = "*"
|
74
contrib/external-tests/rust/src/main.rs
Normal file
74
contrib/external-tests/rust/src/main.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
extern crate screech;
|
||||
extern crate crypto;
|
||||
extern crate time;
|
||||
extern crate rustc_serialize;
|
||||
extern crate byteorder;
|
||||
|
||||
use screech::*;
|
||||
use byteorder::{ByteOrder, BigEndian, LittleEndian};
|
||||
use crypto::curve25519::curve25519_base;
|
||||
use crypto::blake2s::Blake2s;
|
||||
use rustc_serialize::base64::FromBase64;
|
||||
use std::net::*;
|
||||
|
||||
fn memcpy(out: &mut [u8], data: &[u8]) {
|
||||
for count in 0..data.len() {
|
||||
out[count] = data[count];
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let send_addr = "test.wireguard.io:51820".to_socket_addrs().unwrap().next().unwrap();
|
||||
let listen_addr = "0.0.0.0:0".to_socket_addrs().unwrap().next().unwrap();
|
||||
let socket = UdpSocket::bind(listen_addr).unwrap();
|
||||
let mut empty_payload = [0; 0];
|
||||
|
||||
let mut their_public = [0; 32];
|
||||
memcpy(&mut their_public, &"qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM=".from_base64().unwrap());
|
||||
let mut my_private = [0; 32];
|
||||
memcpy(&mut my_private, &"WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo=".from_base64().unwrap());
|
||||
let mut my_preshared = [0; 32];
|
||||
memcpy(&mut my_preshared, &"FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=".from_base64().unwrap());
|
||||
let my_public = curve25519_base(&my_private);
|
||||
let mut my_keypair : Dh25519 = Default::default();
|
||||
my_keypair.set(&my_private, &my_public);
|
||||
let mut owner : HandshakeCryptoOwner<RandomOs, Dh25519, CipherChaChaPoly, HashBLAKE2s> = Default::default();
|
||||
owner.set_s(my_keypair);
|
||||
owner.set_rs(&their_public);
|
||||
let mut cipherstate1 : CipherState<CipherChaChaPoly> = Default::default();
|
||||
let mut cipherstate2 : CipherState<CipherChaChaPoly> = Default::default();
|
||||
let mut handshake = HandshakeState::new_from_owner(&mut owner, true, HandshakePattern::IK, "WireGuard v0 zx2c4 Jason@zx2c4.com".as_bytes(), Some(&my_preshared[..]), &mut cipherstate1, &mut cipherstate2);
|
||||
|
||||
let now = time::get_time();
|
||||
let mut tai64n = [0; 12];
|
||||
BigEndian::write_i64(&mut tai64n[0..], now.sec);
|
||||
BigEndian::write_i32(&mut tai64n[8..], now.nsec);
|
||||
let mut initiation_packet = [0; 145];
|
||||
initiation_packet[0] = 1; /* Type: Initiation */
|
||||
LittleEndian::write_u32(&mut initiation_packet[1..], 28); /* Sender index: 28 (arbitrary) */
|
||||
handshake.write_message(&tai64n, &mut initiation_packet[5..]);
|
||||
let mut mac_material = [0; 143];
|
||||
memcpy(&mut mac_material, &their_public);
|
||||
memcpy(&mut mac_material[32..], &initiation_packet[0..113]);
|
||||
let mut mac = [0; 16];
|
||||
Blake2s::blake2s(&mut mac, &mac_material, &my_preshared);
|
||||
memcpy(&mut initiation_packet[113..], &mac);
|
||||
socket.send_to(&initiation_packet, &send_addr).unwrap();
|
||||
|
||||
let mut response_packet = [0; 89];
|
||||
socket.recv_from(&mut response_packet).unwrap();
|
||||
assert!(response_packet[0] == 2 /* Type: Response */);
|
||||
let their_index = LittleEndian::read_u32(&response_packet[1..]);
|
||||
let our_index = LittleEndian::read_u32(&response_packet[5..]);
|
||||
assert!(our_index == 28);
|
||||
let (payload_len, last) = handshake.read_message(&response_packet[9..57], &mut empty_payload).unwrap();
|
||||
assert!(payload_len == 0 && last);
|
||||
|
||||
let mut keepalive_packet = [0; 29];
|
||||
keepalive_packet[0] = 4; /* Type: Data */
|
||||
LittleEndian::write_u32(&mut keepalive_packet[1..], their_index);
|
||||
LittleEndian::write_u64(&mut keepalive_packet[5..], cipherstate1.n);
|
||||
cipherstate1.encrypt(&empty_payload, &mut keepalive_packet[13..]); /* Empty payload means keepalive */
|
||||
socket.send_to(&keepalive_packet, &send_addr).unwrap();
|
||||
}
|
27
contrib/stress-testing/badpacket.c
Normal file
27
contrib/stress-testing/badpacket.c
Normal file
|
@ -0,0 +1,27 @@
|
|||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const unsigned char handshake1[143] = { 1, 0 };
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(atoi(argv[2])),
|
||||
.sin_addr = inet_addr(argv[1])
|
||||
};
|
||||
connect(fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
|
||||
for (;;)
|
||||
send(fd, handshake1, sizeof(handshake1), 0);
|
||||
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
50
contrib/stress-testing/peg.c
Normal file
50
contrib/stress-testing/peg.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <linux/limits.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static unsigned long long interface_tx_bytes(const char *interface)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
FILE *f;
|
||||
unsigned long long ret;
|
||||
snprintf(buf, PATH_MAX - 1, "/sys/class/net/%s/statistics/tx_bytes", interface);
|
||||
f = fopen(buf, "r");
|
||||
fscanf(f, "%llu", &ret);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char buf[1500] = { 0 };
|
||||
unsigned long long before, after, i;
|
||||
struct timespec begin, end;
|
||||
double elapsed;
|
||||
struct ifreq req;
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(7271),
|
||||
.sin_addr = inet_addr(argv[3])
|
||||
};
|
||||
strcpy(req.ifr_name, argv[1]);
|
||||
ioctl(fd, SIOCGIFMTU, &req);
|
||||
|
||||
connect(fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
|
||||
before = interface_tx_bytes(argv[2]);
|
||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
||||
for (i = 0; i < 10000000; ++i)
|
||||
send(fd, buf, req.ifr_mtu - 28, 0);
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
after = interface_tx_bytes(argv[2]);
|
||||
elapsed = end.tv_sec - begin.tv_sec + (end.tv_nsec - begin.tv_nsec) / 1000000000.0;
|
||||
|
||||
printf("%.4f mbps\n", ((after - before) * 8) / elapsed / 1000000.0);
|
||||
return 0;
|
||||
}
|
48
contrib/stress-testing/self-send.sh
Executable file
48
contrib/stress-testing/self-send.sh
Executable file
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PRIVATE_KEYS=("")
|
||||
PUBLIC_KEYS=("")
|
||||
|
||||
resetwg() {
|
||||
for i in {1..64}; do
|
||||
ip link delete dev wg${i} 2>/dev/null >/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
for i in {1..64}; do
|
||||
next_key="$(wg genkey)"
|
||||
PRIVATE_KEYS+=("$next_key")
|
||||
PUBLIC_KEYS+=($(wg pubkey <<<"$next_key"))
|
||||
done
|
||||
|
||||
resetwg
|
||||
trap resetwg INT TERM EXIT
|
||||
|
||||
for i in {1..64}; do
|
||||
{ echo "[Interface]"
|
||||
echo "ListenPort = $(( $i + 31222 ))"
|
||||
echo "PrivateKey = ${PRIVATE_KEYS[$i]}"
|
||||
|
||||
for j in {1..64}; do
|
||||
[[ $i == $j ]] && continue
|
||||
echo "[Peer]"
|
||||
echo "PublicKey = ${PUBLIC_KEYS[$j]}"
|
||||
echo "AllowedIPs = 192.168.8.${j}/32"
|
||||
echo "Endpoint = 127.0.0.1:$(( $j + 31222 ))"
|
||||
done
|
||||
} > "/tmp/deviceload.conf"
|
||||
|
||||
ip link add dev wg${i} type wireguard
|
||||
wg setconf wg${i} "/tmp/deviceload.conf"
|
||||
ip link set up dev wg${i}
|
||||
rm "/tmp/deviceload.conf"
|
||||
done
|
||||
|
||||
ip address add dev wg1 192.168.8.1/24
|
||||
|
||||
while true; do
|
||||
for i in {2..64}; do
|
||||
echo hello | ncat -u 192.168.8.${i} 1234
|
||||
done
|
||||
done
|
30
contrib/stress-testing/threewayiperf.sh
Executable file
30
contrib/stress-testing/threewayiperf.sh
Executable file
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ $(hostname) == "thinkpad" ]]; then
|
||||
make -C "$(dirname "$0")/../../src" remote-run
|
||||
for i in 128 129 130; do
|
||||
scp "$0" root@172.16.48.${i}:
|
||||
done
|
||||
for i in 128 129 130; do
|
||||
konsole --new-tab -e ssh -t root@172.16.48.${i} "./$(basename "$0")"
|
||||
done
|
||||
exit
|
||||
fi
|
||||
|
||||
# perf top -U --dsos '[wireguard]'
|
||||
|
||||
tmux new-session -s bigtest -d
|
||||
tmux new-window -n "server 6000" -t bigtest "iperf3 -p 6000 -s"
|
||||
tmux new-window -n "server 6001" -t bigtest "iperf3 -p 6001 -s"
|
||||
sleep 5
|
||||
me=$(ip -o -4 address show dev wg0 | sed 's/.*inet \([^ ]*\)\/.*/\1/' | cut -d . -f 4)
|
||||
for i in 1 2 3; do
|
||||
[[ $i == $me ]] && continue
|
||||
[[ $me == "1" ]] && port=6000
|
||||
[[ $me == "3" ]] && port=6001
|
||||
[[ $me == "2" && $i == "1" ]] && port=6000
|
||||
[[ $me == "2" && $i == "3" ]] && port=6001
|
||||
tmux new-window -n "client 192.168.2.${i}" -t bigtest "iperf3 -n 300000G -i 1 -p $port -c 192.168.2.${i}"
|
||||
done
|
||||
tmux attach -t bigtest
|
15
contrib/wgserver.service
Normal file
15
contrib/wgserver.service
Normal file
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=WireGuard Server
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/bin/ip link add dev wgserver type wireguard
|
||||
ExecStart=/bin/ip address add 192.168.177.1/24 dev wgserver
|
||||
ExecStart=/usr/bin/wg setconf wgserver /etc/wireguard-server.conf
|
||||
ExecStart=/bin/ip link set up dev wgserver
|
||||
ExecStop=/bin/sh -c 'umask 077; /usr/bin/wg showconf wgserver > /etc/wireguard-server.conf.tmp && mv /etc/wireguard-server.conf.tmp /etc/wireguard-server.conf'
|
||||
ExecStop=/bin/ip link del dev wgserver
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
3
src/.gitignore
vendored
Normal file
3
src/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.d
|
||||
*.o
|
||||
wg
|
26
src/Makefile
Normal file
26
src/Makefile
Normal file
|
@ -0,0 +1,26 @@
|
|||
PREFIX ?= /usr
|
||||
DESTDIR ?=
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
LIBDIR ?= $(PREFIX)/lib
|
||||
MANDIR ?= $(PREFIX)/share/man
|
||||
|
||||
CFLAGS += -std=gnu11
|
||||
CFLAGS += -pedantic -Wall -Wextra
|
||||
CFLAGS += -MMD
|
||||
LDLIBS += -lresolv -lmnl
|
||||
|
||||
wg: $(patsubst %.c,%.o,$(wildcard *.c))
|
||||
|
||||
clean:
|
||||
rm -f wg *.o *.d
|
||||
|
||||
install: wg
|
||||
install -v -d "$(DESTDIR)$(BINDIR)" && install -s -m 0755 -v wg "$(DESTDIR)$(BINDIR)/wg"
|
||||
install -v -d "$(DESTDIR)$(MANDIR)/man8" && install -m 0644 -v wg.8 "$(DESTDIR)$(MANDIR)/man8/wg.8"
|
||||
|
||||
check: clean
|
||||
CFLAGS=-g scan-build --view --keep-going $(MAKE) wg
|
||||
|
||||
.PHONY: clean install check
|
||||
|
||||
-include *.d
|
220
src/base64.c
Normal file
220
src/base64.c
Normal file
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright (c) 1996, 1998 by Internet Software Consortium.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
|
||||
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
|
||||
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
||||
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Portions Copyright (c) 1995 by International Business Machines, Inc.
|
||||
*
|
||||
* International Business Machines, Inc. (hereinafter called IBM) grants
|
||||
* permission under its copyrights to use, copy, modify, and distribute this
|
||||
* Software with or without fee, provided that the above copyright notice and
|
||||
* all paragraphs of this notice appear in all copies, and that the name of IBM
|
||||
* not be used in connection with the marketing of any product incorporating
|
||||
* the Software or modifications thereof, without specific, written prior
|
||||
* permission.
|
||||
*
|
||||
* To the extent it has a right to do so, IBM grants an immunity from suit
|
||||
* under its patents, if any, for the use, sale or manufacture of products to
|
||||
* the extent that such products are used for performing Domain Name System
|
||||
* dynamic updates in TCP/IP networks by means of the Software. No immunity is
|
||||
* granted for any product per se or for any other function of any product.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
|
||||
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
|
||||
* DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
|
||||
* IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
|
||||
#include "base64.h"
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#if defined(NEED_B64_NTOP) || defined(NEED_B64_PTON)
|
||||
static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static const char pad64 = '=';
|
||||
#endif
|
||||
|
||||
#ifdef NEED_B64_NTOP
|
||||
int b64_ntop(unsigned char const *src, size_t srclength, char *target, size_t targsize)
|
||||
{
|
||||
size_t datalength = 0;
|
||||
uint8_t input[3];
|
||||
uint8_t output[4];
|
||||
size_t i;
|
||||
|
||||
while (2 < srclength) {
|
||||
input[0] = *src++;
|
||||
input[1] = *src++;
|
||||
input[2] = *src++;
|
||||
srclength -= 3;
|
||||
|
||||
output[0] = input[0] >> 2;
|
||||
output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
|
||||
output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
|
||||
output[3] = input[2] & 0x3f;
|
||||
assert(output[0] < 64);
|
||||
assert(output[1] < 64);
|
||||
assert(output[2] < 64);
|
||||
assert(output[3] < 64);
|
||||
|
||||
if (datalength + 4 > targsize)
|
||||
return -1;
|
||||
target[datalength++] = base64[output[0]];
|
||||
target[datalength++] = base64[output[1]];
|
||||
target[datalength++] = base64[output[2]];
|
||||
target[datalength++] = base64[output[3]];
|
||||
}
|
||||
if (0 != srclength) {
|
||||
input[0] = input[1] = input[2] = '\0';
|
||||
for (i = 0; i < srclength; i++)
|
||||
input[i] = *src++;
|
||||
output[0] = input[0] >> 2;
|
||||
output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
|
||||
output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
|
||||
assert(output[0] < 64);
|
||||
assert(output[1] < 64);
|
||||
assert(output[2] < 64);
|
||||
|
||||
if (datalength + 4 > targsize)
|
||||
return -1;
|
||||
target[datalength++] = base64[output[0]];
|
||||
target[datalength++] = base64[output[1]];
|
||||
if (srclength == 1)
|
||||
target[datalength++] = pad64;
|
||||
else
|
||||
target[datalength++] = base64[output[2]];
|
||||
target[datalength++] = pad64;
|
||||
}
|
||||
if (datalength >= targsize)
|
||||
return (-1);
|
||||
target[datalength] = '\0';
|
||||
return datalength;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef NEED_B64_PTON
|
||||
int b64_pton(char const *src, uint8_t *target, size_t targsize)
|
||||
{
|
||||
static int b64rmap_initialized = 0;
|
||||
static uint8_t b64rmap[256];
|
||||
static const uint8_t b64rmap_special = 0xf0;
|
||||
static const uint8_t b64rmap_end = 0xfd;
|
||||
static const uint8_t b64rmap_space = 0xfe;
|
||||
static const uint8_t b64rmap_invalid = 0xff;
|
||||
int tarindex, state, ch;
|
||||
uint8_t ofs;
|
||||
|
||||
if (!b64rmap_initialized) {
|
||||
int i;
|
||||
char ch;
|
||||
b64rmap[0] = b64rmap_end;
|
||||
for (i = 1; i < 256; ++i) {
|
||||
ch = (char)i;
|
||||
if (isspace(ch))
|
||||
b64rmap[i] = b64rmap_space;
|
||||
else if (ch == pad64)
|
||||
b64rmap[i] = b64rmap_end;
|
||||
else
|
||||
b64rmap[i] = b64rmap_invalid;
|
||||
}
|
||||
for (i = 0; base64[i] != '\0'; ++i)
|
||||
b64rmap[(uint8_t)base64[i]] = i;
|
||||
b64rmap_initialized = 1;
|
||||
}
|
||||
|
||||
state = 0;
|
||||
tarindex = 0;
|
||||
|
||||
for (;;) {
|
||||
ch = *src++;
|
||||
ofs = b64rmap[ch];
|
||||
|
||||
if (ofs >= b64rmap_special) {
|
||||
if (ofs == b64rmap_space)
|
||||
continue;
|
||||
if (ofs == b64rmap_end)
|
||||
break;
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
if ((size_t)tarindex >= targsize)
|
||||
return -1;
|
||||
target[tarindex] = ofs << 2;
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if ((size_t)tarindex + 1 >= targsize)
|
||||
return -1;
|
||||
target[tarindex] |= ofs >> 4;
|
||||
target[tarindex+1] = (ofs & 0x0f) << 4 ;
|
||||
tarindex++;
|
||||
state = 2;
|
||||
break;
|
||||
case 2:
|
||||
if ((size_t)tarindex + 1 >= targsize)
|
||||
return -1;
|
||||
target[tarindex] |= ofs >> 2;
|
||||
target[tarindex+1] = (ofs & 0x03) << 6;
|
||||
tarindex++;
|
||||
state = 3;
|
||||
break;
|
||||
case 3:
|
||||
if ((size_t)tarindex >= targsize)
|
||||
return -1;
|
||||
target[tarindex] |= ofs;
|
||||
tarindex++;
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == pad64) {
|
||||
ch = *src++;
|
||||
switch (state) {
|
||||
case 0:
|
||||
case 1:
|
||||
return -1;
|
||||
|
||||
case 2:
|
||||
for (; ch; ch = *src++) {
|
||||
if (b64rmap[ch] != b64rmap_space)
|
||||
break;
|
||||
}
|
||||
if (ch != pad64)
|
||||
return -1;
|
||||
ch = *src++;
|
||||
case 3:
|
||||
for (; ch; ch = *src++) {
|
||||
if (b64rmap[ch] != b64rmap_space)
|
||||
return -1;
|
||||
}
|
||||
if (target[tarindex] != 0)
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (state != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return tarindex;
|
||||
}
|
||||
#endif
|
20
src/base64.h
Normal file
20
src/base64.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
#include <resolv.h>
|
||||
|
||||
#define b64_len(len) ((((len) + 2) / 3) * 4 + 1)
|
||||
|
||||
#ifndef b64_ntop
|
||||
int b64_ntop(unsigned char const *, size_t, char *, size_t);
|
||||
#define NEED_B64_NTOP
|
||||
#endif
|
||||
|
||||
#ifndef b64_pton
|
||||
int b64_pton(char const *, unsigned char *, size_t);
|
||||
#define NEED_B64_PTON
|
||||
#endif
|
||||
|
||||
#endif
|
518
src/config.c
Normal file
518
src/config.c
Normal file
|
@ -0,0 +1,518 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "kernel.h"
|
||||
#include "base64.h"
|
||||
|
||||
#define COMMENT_CHAR '#'
|
||||
|
||||
#define max(a, b) (a > b ? a : b)
|
||||
|
||||
static inline struct wgpeer *peer_from_offset(struct wgdevice *dev, size_t offset)
|
||||
{
|
||||
return (struct wgpeer *)((uint8_t *)dev + sizeof(struct wgdevice) + offset);
|
||||
}
|
||||
|
||||
static int use_space(struct inflatable_device *buf, size_t space)
|
||||
{
|
||||
size_t expand_to;
|
||||
uint8_t *new_dev;
|
||||
|
||||
if (buf->len - buf->pos < space) {
|
||||
expand_to = max(buf->len * 2, buf->len + space);
|
||||
new_dev = realloc(buf->dev, expand_to + sizeof(struct wgdevice));
|
||||
if (!new_dev)
|
||||
return -errno;
|
||||
memset(&new_dev[buf->len + sizeof(struct wgdevice)], 0, expand_to - buf->len);
|
||||
buf->dev = (struct wgdevice *)new_dev;
|
||||
buf->len = expand_to;
|
||||
}
|
||||
buf->pos += space;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *get_value(const char *line, const char *key)
|
||||
{
|
||||
size_t linelen = strlen(line);
|
||||
size_t keylen = strlen(key);
|
||||
|
||||
if (keylen >= linelen)
|
||||
return NULL;
|
||||
|
||||
if (strncasecmp(line, key, keylen))
|
||||
return NULL;
|
||||
|
||||
return line + keylen;
|
||||
}
|
||||
|
||||
static inline uint16_t parse_port(const char *value)
|
||||
{
|
||||
int ret;
|
||||
uint16_t port = 0;
|
||||
struct addrinfo *resolved;
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_DGRAM,
|
||||
.ai_protocol = IPPROTO_UDP,
|
||||
.ai_flags = AI_ADDRCONFIG | AI_PASSIVE
|
||||
};
|
||||
|
||||
if (!strlen(value)) {
|
||||
fprintf(stderr, "Unable to parse empty port\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = getaddrinfo(NULL, value, &hints, &resolved);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "%s: `%s`\n", gai_strerror(ret), value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in))
|
||||
port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
|
||||
else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))
|
||||
port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
|
||||
else
|
||||
fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s`\n", value);
|
||||
|
||||
freeaddrinfo(resolved);
|
||||
return port;
|
||||
}
|
||||
|
||||
static inline bool parse_key(uint8_t key[WG_KEY_LEN], const char *value)
|
||||
{
|
||||
uint8_t tmp[WG_KEY_LEN + 1];
|
||||
if (strlen(value) != b64_len(WG_KEY_LEN) - 1) {
|
||||
fprintf(stderr, "Key is not the correct length: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
if (b64_pton(value, tmp, WG_KEY_LEN + 1) < 0) {
|
||||
fprintf(stderr, "Could not parse base64 key: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
memcpy(key, tmp, WG_KEY_LEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool parse_ip(struct wgipmask *ipmask, const char *value)
|
||||
{
|
||||
ipmask->family = AF_UNSPEC;
|
||||
if (strchr(value, ':')) {
|
||||
if (inet_pton(AF_INET6, value, &ipmask->ip6) == 1)
|
||||
ipmask->family = AF_INET6;
|
||||
} else {
|
||||
if (inet_pton(AF_INET, value, &ipmask->ip4) == 1)
|
||||
ipmask->family = AF_INET;
|
||||
}
|
||||
if (ipmask->family == AF_UNSPEC) {
|
||||
fprintf(stderr, "Unable to parse IP address: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool parse_endpoint(struct sockaddr_storage *endpoint, const char *value)
|
||||
{
|
||||
char *mutable = strdup(value);
|
||||
char *begin, *end;
|
||||
int ret;
|
||||
struct addrinfo *resolved;
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_DGRAM,
|
||||
.ai_protocol = IPPROTO_UDP,
|
||||
.ai_flags = AI_ADDRCONFIG
|
||||
};
|
||||
if (!strlen(value)) {
|
||||
free(mutable);
|
||||
fprintf(stderr, "Unable to parse empty endpoint\n");
|
||||
return false;
|
||||
}
|
||||
if (mutable[0] == '[') {
|
||||
begin = &mutable[1];
|
||||
end = strchr(mutable, ']');
|
||||
if (!end) {
|
||||
free(mutable);
|
||||
fprintf(stderr, "Unable to find matching brace of endpoint: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
*end = '\0';
|
||||
++end;
|
||||
if (*end != ':' || !*(end + 1)) {
|
||||
free(mutable);
|
||||
fprintf(stderr, "Unable to find port of endpoint: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
++end;
|
||||
} else {
|
||||
begin = mutable;
|
||||
end = strrchr(mutable, ':');
|
||||
if (!end || !*(end + 1)) {
|
||||
free(mutable);
|
||||
fprintf(stderr, "Unable to find port of endpoint: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
*end = '\0';
|
||||
++end;
|
||||
}
|
||||
ret = getaddrinfo(begin, end, &hints, &resolved);
|
||||
if (ret != 0) {
|
||||
free(mutable);
|
||||
fprintf(stderr, "%s: `%s`\n", gai_strerror(ret), value);
|
||||
return false;
|
||||
}
|
||||
if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
|
||||
(resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)))
|
||||
memcpy(endpoint, resolved->ai_addr, resolved->ai_addrlen);
|
||||
else {
|
||||
freeaddrinfo(resolved);
|
||||
free(mutable);
|
||||
fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s`\n", value);
|
||||
return false;
|
||||
}
|
||||
freeaddrinfo(resolved);
|
||||
free(mutable);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offset, const char *value)
|
||||
{
|
||||
struct wgpeer *peer;
|
||||
struct wgipmask *ipmask;
|
||||
char *mask, *mutable = strdup(value), *sep;
|
||||
if (!mutable) {
|
||||
perror("strdup");
|
||||
return false;
|
||||
};
|
||||
peer = peer_from_offset(buf->dev, peer_offset);
|
||||
peer->num_ipmasks = 0;
|
||||
peer->replace_ipmasks = true;
|
||||
if (!strlen(value)) {
|
||||
free(mutable);
|
||||
return true;
|
||||
}
|
||||
sep = mutable;
|
||||
while ((mask = strsep(&sep, ","))) {
|
||||
unsigned long cidr;
|
||||
char *end, *ip = strsep(&mask, "/");
|
||||
if (use_space(buf, sizeof(struct wgipmask)) < 0) {
|
||||
perror("use_space");
|
||||
free(mutable);
|
||||
return false;
|
||||
}
|
||||
peer = peer_from_offset(buf->dev, peer_offset);
|
||||
ipmask = (struct wgipmask *)((uint8_t *)peer + sizeof(struct wgpeer) + (sizeof(struct wgipmask) * peer->num_ipmasks));
|
||||
|
||||
if (!parse_ip(ipmask, ip)) {
|
||||
free(mutable);
|
||||
return false;
|
||||
}
|
||||
if (ipmask->family == AF_INET) {
|
||||
if (mask) {
|
||||
cidr = strtoul(mask, &end, 10);
|
||||
if (*end)
|
||||
mask = NULL;
|
||||
if (cidr > 32)
|
||||
mask = NULL;
|
||||
}
|
||||
if (!mask)
|
||||
cidr = 32;
|
||||
} else if (ipmask->family == AF_INET6) {
|
||||
if (mask) {
|
||||
cidr = strtoul(mask, &end, 10);
|
||||
if (*end)
|
||||
mask = NULL;
|
||||
if (cidr > 128)
|
||||
mask = NULL;
|
||||
}
|
||||
if (!mask)
|
||||
cidr = 128;
|
||||
} else
|
||||
continue;
|
||||
ipmask->cidr = cidr;
|
||||
++peer->num_ipmasks;
|
||||
}
|
||||
free(mutable);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_line(struct config_ctx *ctx, const char *line)
|
||||
{
|
||||
const char *value;
|
||||
bool ret = true;
|
||||
|
||||
if (!strcasecmp(line, "[Interface]")) {
|
||||
ctx->is_peer_section = false;
|
||||
ctx->is_device_section = true;
|
||||
return true;
|
||||
}
|
||||
if (!strcasecmp(line, "[Peer]")) {
|
||||
ctx->peer_offset = ctx->buf.pos;
|
||||
if (use_space(&ctx->buf, sizeof(struct wgpeer)) < 0) {
|
||||
perror("use_space");
|
||||
return false;
|
||||
}
|
||||
++ctx->buf.dev->num_peers;
|
||||
ctx->is_peer_section = true;
|
||||
ctx->is_device_section = false;
|
||||
peer_from_offset(ctx->buf.dev, ctx->peer_offset)->replace_ipmasks = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define key_match(key) (value = get_value(line, key "="))
|
||||
|
||||
if (ctx->is_device_section) {
|
||||
if (key_match("ListenPort"))
|
||||
ret = !!(ctx->buf.dev->port = parse_port(value));
|
||||
else if (key_match("PrivateKey")) {
|
||||
ret = parse_key(ctx->buf.dev->private_key, value);
|
||||
if (!ret)
|
||||
memset(ctx->buf.dev->private_key, 0, WG_KEY_LEN);
|
||||
} else if (key_match("PresharedKey")) {
|
||||
ret = parse_key(ctx->buf.dev->preshared_key, value);
|
||||
if (!ret)
|
||||
memset(ctx->buf.dev->preshared_key, 0, WG_KEY_LEN);
|
||||
} else
|
||||
goto error;
|
||||
} else if (ctx->is_peer_section) {
|
||||
if (key_match("Endpoint"))
|
||||
ret = parse_endpoint(&peer_from_offset(ctx->buf.dev, ctx->peer_offset)->endpoint, value);
|
||||
else if (key_match("PublicKey"))
|
||||
ret = parse_key(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->public_key, value);
|
||||
else if (key_match("AllowedIPs"))
|
||||
ret = parse_ipmasks(&ctx->buf, ctx->peer_offset, value);
|
||||
else
|
||||
goto error;
|
||||
} else
|
||||
goto error;
|
||||
return ret;
|
||||
|
||||
#undef key_match
|
||||
|
||||
error:
|
||||
fprintf(stderr, "Line unrecognized: `%s'\n", line);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_read_line(struct config_ctx *ctx, const char *input)
|
||||
{
|
||||
size_t len = strlen(input), cleaned_len = 0;
|
||||
char *line = calloc(len + 1, sizeof(char));
|
||||
bool ret = true;
|
||||
if (!line) {
|
||||
perror("calloc");
|
||||
return false;
|
||||
}
|
||||
if (!len)
|
||||
goto out;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (!isspace(input[i]))
|
||||
line[cleaned_len++] = input[i];
|
||||
}
|
||||
if (!cleaned_len)
|
||||
goto out;
|
||||
if (line[0] == COMMENT_CHAR)
|
||||
goto out;
|
||||
ret = process_line(ctx, line);
|
||||
out:
|
||||
free(line);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append)
|
||||
{
|
||||
memset(ctx, 0, sizeof(struct config_ctx));
|
||||
ctx->device = device;
|
||||
ctx->buf.dev = calloc(1, sizeof(struct wgdevice));
|
||||
if (!ctx->buf.dev) {
|
||||
perror("calloc");
|
||||
return false;
|
||||
}
|
||||
ctx->buf.dev->replace_peer_list = !append;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool key_is_valid(uint8_t key[WG_KEY_LEN])
|
||||
{
|
||||
static const uint8_t zero[WG_KEY_LEN] = { 0 };
|
||||
return !!memcmp(key, zero, WG_KEY_LEN);
|
||||
}
|
||||
|
||||
bool config_read_finish(struct config_ctx *ctx)
|
||||
{
|
||||
size_t i;
|
||||
struct wgpeer *peer;
|
||||
if (ctx->buf.dev->replace_peer_list && !ctx->buf.dev->num_peers) {
|
||||
fprintf(stderr, "No peers configured\n");
|
||||
goto err;
|
||||
}
|
||||
if (ctx->buf.dev->replace_peer_list && !key_is_valid(ctx->buf.dev->private_key)) {
|
||||
fprintf(stderr, "No private key configured\n");
|
||||
goto err;
|
||||
}
|
||||
for_each_wgpeer(ctx->buf.dev, peer, i) {
|
||||
if (!key_is_valid(peer->public_key)) {
|
||||
fprintf(stderr, "A peer is missing a public key\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
*ctx->device = ctx->buf.dev;
|
||||
return true;
|
||||
err:
|
||||
free(ctx->buf.dev);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int read_line(char **dst, const char *path)
|
||||
{
|
||||
FILE *f;
|
||||
size_t n = 0;
|
||||
struct stat stat;
|
||||
|
||||
*dst = NULL;
|
||||
|
||||
f = fopen(path, "r");
|
||||
if (!f) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
if (fstat(fileno(f), &stat) < 0) {
|
||||
perror("fstat");
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (S_ISCHR(stat.st_mode) && stat.st_rdev == makedev(1, 3)) {
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
if (getline(dst, &n, f) < 0) {
|
||||
perror("getline");
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
n = strlen(*dst);
|
||||
while (--n) {
|
||||
if (isspace((*dst)[n]))
|
||||
(*dst)[n] = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *strip_spaces(const char *in)
|
||||
{
|
||||
char *out;
|
||||
size_t t, l, i;
|
||||
|
||||
t = strlen(in);
|
||||
out = calloc(t + 1, sizeof(char));
|
||||
if (!out) {
|
||||
perror("calloc");
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0, l = 0; i < t; ++i) {
|
||||
if (!isspace(in[i]))
|
||||
out[l++] = in[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool config_read_cmd(struct wgdevice **device, char *argv[], int argc)
|
||||
{
|
||||
struct inflatable_device buf = { 0 };
|
||||
size_t peer_offset = 0;
|
||||
buf.dev = calloc(sizeof(struct wgdevice), 1);
|
||||
if (!buf.dev) {
|
||||
perror("calloc");
|
||||
return false;
|
||||
}
|
||||
while (argc > 0) {
|
||||
if (!strcmp(argv[0], "listen-port") && argc >= 2 && !buf.dev->num_peers) {
|
||||
buf.dev->port = parse_port(argv[1]);
|
||||
if (!buf.dev->port)
|
||||
goto error;
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else if (!strcmp(argv[0], "private-key") && argc >= 2 && !buf.dev->num_peers) {
|
||||
char *line;
|
||||
int ret = read_line(&line, argv[1]);
|
||||
if (ret == 0) {
|
||||
if (!parse_key(buf.dev->private_key, line)) {
|
||||
free(line);
|
||||
goto error;
|
||||
}
|
||||
free(line);
|
||||
} else if (ret == 1)
|
||||
buf.dev->remove_private_key = true;
|
||||
else
|
||||
goto error;
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && !buf.dev->num_peers) {
|
||||
char *line;
|
||||
int ret = read_line(&line, argv[1]);
|
||||
if (ret == 0) {
|
||||
if (!parse_key(buf.dev->preshared_key, line)) {
|
||||
free(line);
|
||||
goto error;
|
||||
}
|
||||
free(line);
|
||||
} else if (ret == 1)
|
||||
buf.dev->remove_preshared_key = true;
|
||||
else
|
||||
goto error;
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else if (!strcmp(argv[0], "peer") && argc >= 2) {
|
||||
peer_offset = buf.pos;
|
||||
if (use_space(&buf, sizeof(struct wgpeer)) < 0) {
|
||||
perror("use_space");
|
||||
goto error;
|
||||
}
|
||||
++buf.dev->num_peers;
|
||||
if (!parse_key(peer_from_offset(buf.dev, peer_offset)->public_key, argv[1]))
|
||||
goto error;
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else if (!strcmp(argv[0], "remove") && argc >= 1 && buf.dev->num_peers) {
|
||||
peer_from_offset(buf.dev, peer_offset)->remove_me = true;
|
||||
argv += 1;
|
||||
argc -= 1;
|
||||
} else if (!strcmp(argv[0], "endpoint") && argc >= 2 && buf.dev->num_peers) {
|
||||
if (!parse_endpoint(&peer_from_offset(buf.dev, peer_offset)->endpoint, argv[1]))
|
||||
goto error;
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && buf.dev->num_peers) {
|
||||
char *line = strip_spaces(argv[1]);
|
||||
if (!line)
|
||||
goto error;
|
||||
if (!parse_ipmasks(&buf, peer_offset, line)) {
|
||||
free(line);
|
||||
goto error;
|
||||
}
|
||||
free(line);
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
*device = buf.dev;
|
||||
return true;
|
||||
error:
|
||||
free(buf.dev);
|
||||
return false;
|
||||
}
|
34
src/config.h
Normal file
34
src/config.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "../uapi.h"
|
||||
|
||||
struct inflatable_device {
|
||||
struct wgdevice *dev;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
struct config_ctx {
|
||||
struct inflatable_device buf;
|
||||
size_t peer_offset;
|
||||
struct wgdevice **device;
|
||||
bool is_peer_section;
|
||||
bool is_device_section;
|
||||
};
|
||||
|
||||
bool config_read_cmd(struct wgdevice **dev, char *argv[], int argc);
|
||||
bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append);
|
||||
bool config_read_line(struct config_ctx *ctx, const char *line);
|
||||
bool config_read_finish(struct config_ctx *ctx);
|
||||
|
||||
#endif
|
1258
src/curve25519.c
Normal file
1258
src/curve25519.c
Normal file
File diff suppressed because it is too large
Load diff
22
src/curve25519.h
Normal file
22
src/curve25519.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#ifndef CURVE25519_H
|
||||
#define CURVE25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
enum curve25519_lengths {
|
||||
CURVE25519_POINT_SIZE = 32,
|
||||
};
|
||||
|
||||
void curve25519(uint8_t *mypublic, const uint8_t *secret, const uint8_t *basepoint);
|
||||
void curve25519_generate_public(uint8_t *pub, const uint8_t *secret);
|
||||
static inline void curve25519_normalize_secret(uint8_t secret[CURVE25519_POINT_SIZE])
|
||||
{
|
||||
secret[0] &= 248;
|
||||
secret[31] &= 127;
|
||||
secret[31] |= 64;
|
||||
}
|
||||
|
||||
#endif
|
59
src/genkey.c
Normal file
59
src/genkey.c
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <syscall.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "curve25519.h"
|
||||
#include "base64.h"
|
||||
|
||||
#ifdef __NR_getrandom
|
||||
static inline ssize_t get_random_bytes(uint8_t *out, size_t len)
|
||||
{
|
||||
return syscall(__NR_getrandom, out, len, 0);
|
||||
}
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
static inline ssize_t get_random_bytes(uint8_t *out, size_t len)
|
||||
{
|
||||
ssize_t ret;
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
ret = read(fd, out, len);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
int genkey_main(int argc, char *argv[])
|
||||
{
|
||||
unsigned char private_key[CURVE25519_POINT_SIZE];
|
||||
char private_key_base64[b64_len(CURVE25519_POINT_SIZE)];
|
||||
struct stat stat;
|
||||
|
||||
if (!fstat(STDOUT_FILENO, &stat) && S_ISREG(stat.st_mode) && stat.st_mode & S_IRWXO)
|
||||
fputs("Warning: writing to world accessible file.\nConsider setting the umask to 077 and trying again.\n", stderr);
|
||||
|
||||
if (get_random_bytes(private_key, CURVE25519_POINT_SIZE) != CURVE25519_POINT_SIZE) {
|
||||
perror("getrandom");
|
||||
return 1;
|
||||
}
|
||||
if (argc && !strcmp(argv[0], "genkey"))
|
||||
curve25519_normalize_secret(private_key);
|
||||
|
||||
if (b64_ntop(private_key, sizeof(private_key), private_key_base64, sizeof(private_key_base64)) < 0) {
|
||||
errno = EINVAL;
|
||||
perror("b64");
|
||||
return 1;
|
||||
}
|
||||
|
||||
puts(private_key_base64);
|
||||
return 0;
|
||||
|
||||
}
|
242
src/kernel.c
Normal file
242
src/kernel.c
Normal file
|
@ -0,0 +1,242 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <errno.h>
|
||||
#include <libmnl/libmnl.h>
|
||||
#include <linux/if_link.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "kernel.h"
|
||||
#include "../uapi.h"
|
||||
|
||||
struct inflatable_buffer {
|
||||
char *buffer;
|
||||
char *next;
|
||||
bool good;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
#define max(a, b) (a > b ? a : b)
|
||||
|
||||
static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
|
||||
{
|
||||
size_t len, expand_to;
|
||||
char *new_buffer;
|
||||
|
||||
if (!buffer->good || !buffer->next) {
|
||||
free(buffer->next);
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = strlen(buffer->next) + 1;
|
||||
|
||||
if (len == 1)
|
||||
return 0;
|
||||
|
||||
if (buffer->len - buffer->pos <= len) {
|
||||
expand_to = max(buffer->len * 2, buffer->len + len + 1);
|
||||
new_buffer = realloc(buffer->buffer, expand_to);
|
||||
if (!new_buffer) {
|
||||
free(buffer->next);
|
||||
return -errno;
|
||||
}
|
||||
memset(&new_buffer[buffer->len], 0, expand_to - buffer->len);
|
||||
buffer->buffer = new_buffer;
|
||||
buffer->len = expand_to;
|
||||
}
|
||||
memcpy(&buffer->buffer[buffer->pos], buffer->next, len);
|
||||
free(buffer->next);
|
||||
buffer->pos += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_linkinfo(const struct nlattr *attr, void *data)
|
||||
{
|
||||
struct inflatable_buffer *buffer = data;
|
||||
if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp("wireguard", mnl_attr_get_str(attr)))
|
||||
buffer->good = true;
|
||||
return MNL_CB_OK;
|
||||
}
|
||||
|
||||
static int parse_infomsg(const struct nlattr *attr, void *data)
|
||||
{
|
||||
struct inflatable_buffer *buffer = data;
|
||||
if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
|
||||
return mnl_attr_parse_nested(attr, parse_linkinfo, data);
|
||||
else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
|
||||
buffer->next = strdup(mnl_attr_get_str(attr));
|
||||
return MNL_CB_OK;
|
||||
}
|
||||
|
||||
static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
|
||||
{
|
||||
struct inflatable_buffer *buffer = data;
|
||||
buffer->good = false;
|
||||
buffer->next = NULL;
|
||||
int ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, data);
|
||||
if (ret != MNL_CB_OK)
|
||||
return ret;
|
||||
ret = add_next_to_inflatable_buffer(buffer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (nlh->nlmsg_type != NLMSG_DONE)
|
||||
return MNL_CB_OK + 1;
|
||||
return MNL_CB_OK;
|
||||
}
|
||||
|
||||
/* first\0second\0third\0forth\0last\0\0 */
|
||||
char *kernel_get_wireguard_interfaces(void)
|
||||
{
|
||||
struct mnl_socket *nl = NULL;
|
||||
char *rtnl_buffer = NULL;
|
||||
size_t message_len;
|
||||
unsigned int portid, seq;
|
||||
ssize_t len;
|
||||
int ret = 0;
|
||||
struct inflatable_buffer buffer = { 0 };
|
||||
struct nlmsghdr *nlh;
|
||||
struct ifinfomsg *ifm;
|
||||
|
||||
buffer.len = 4096;
|
||||
buffer.buffer = calloc(buffer.len, 1);
|
||||
if (!buffer.buffer) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rtnl_buffer = calloc(4096, 1);
|
||||
if (!rtnl_buffer) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
nl = mnl_socket_open(NETLINK_ROUTE);
|
||||
if (!nl) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
seq = time(NULL);
|
||||
portid = mnl_socket_get_portid(nl);
|
||||
nlh = mnl_nlmsg_put_header(rtnl_buffer);
|
||||
nlh->nlmsg_type = RTM_GETLINK;
|
||||
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
|
||||
nlh->nlmsg_seq = seq;
|
||||
ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
|
||||
ifm->ifi_family = AF_UNSPEC;
|
||||
message_len = nlh->nlmsg_len;
|
||||
|
||||
if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
another:
|
||||
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, 4096)) < 0) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, &buffer)) < 0) {
|
||||
ret = -errno;
|
||||
goto cleanup;
|
||||
}
|
||||
if (len == MNL_CB_OK + 1)
|
||||
goto another;
|
||||
|
||||
cleanup:
|
||||
free(rtnl_buffer);
|
||||
if (nl)
|
||||
mnl_socket_close(nl);
|
||||
errno = -ret;
|
||||
if (errno) {
|
||||
perror("Error when trying to get a list of Wireguard interfaces");
|
||||
free(buffer.buffer);
|
||||
return NULL;
|
||||
}
|
||||
return buffer.buffer;
|
||||
}
|
||||
|
||||
bool kernel_has_wireguard_interface(const char *interface)
|
||||
{
|
||||
char *interfaces, *this_interface;
|
||||
this_interface = interfaces = kernel_get_wireguard_interfaces();
|
||||
if (!interfaces)
|
||||
return false;
|
||||
for (size_t len = 0; (len = strlen(this_interface)); this_interface += len + 1) {
|
||||
if (!strcmp(interface, this_interface)) {
|
||||
free(interfaces);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
free(interfaces);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int do_ioctl(int req, struct ifreq *ifreq)
|
||||
{
|
||||
static int fd = -1;
|
||||
if (fd < 0) {
|
||||
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
}
|
||||
return ioctl(fd, req, ifreq);
|
||||
}
|
||||
|
||||
int kernel_set_device(struct wgdevice *dev)
|
||||
{
|
||||
struct ifreq ifreq = { .ifr_data = (char *)dev };
|
||||
memcpy(&ifreq.ifr_name, dev->interface, IFNAMSIZ);
|
||||
ifreq.ifr_name[IFNAMSIZ - 1] = 0;
|
||||
return do_ioctl(WG_SET_DEVICE, &ifreq);
|
||||
}
|
||||
|
||||
int kernel_get_device(struct wgdevice **dev, const char *interface)
|
||||
{
|
||||
int ret;
|
||||
struct ifreq ifreq = { 0 };
|
||||
memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
|
||||
ifreq.ifr_name[IFNAMSIZ - 1] = 0;
|
||||
*dev = NULL;
|
||||
do {
|
||||
free(*dev);
|
||||
ret = do_ioctl(WG_GET_DEVICE, &ifreq);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
*dev = calloc(ret + sizeof(struct wgdevice), 1);
|
||||
if (!*dev) {
|
||||
perror("calloc");
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
(*dev)->peers_size = ret;
|
||||
ifreq.ifr_data = (char *)*dev;
|
||||
memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
|
||||
ifreq.ifr_name[IFNAMSIZ - 1] = 0;
|
||||
ret = do_ioctl(WG_GET_DEVICE, &ifreq);
|
||||
} while (ret == -EMSGSIZE);
|
||||
if (ret < 0) {
|
||||
free(*dev);
|
||||
*dev = NULL;
|
||||
}
|
||||
out:
|
||||
errno = -ret;
|
||||
return ret;
|
||||
}
|
24
src/kernel.h
Normal file
24
src/kernel.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#ifndef KERNEL_H
|
||||
#define KERNEL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct wgdevice;
|
||||
|
||||
int kernel_set_device(struct wgdevice *dev);
|
||||
int kernel_get_device(struct wgdevice **dev, const char *interface);
|
||||
char *kernel_get_wireguard_interfaces(void);
|
||||
bool kernel_has_wireguard_interface(const char *interface);
|
||||
|
||||
|
||||
#define for_each_wgpeer(__dev, __peer, __i) for ((__i) = 0, (__peer) = (typeof(__peer))((uint8_t *)(__dev) + sizeof(struct wgdevice)); \
|
||||
(__i) < (__dev)->num_peers; \
|
||||
++(__i), (__peer) = (typeof(__peer))((uint8_t *)(__peer) + sizeof(struct wgpeer) + (sizeof(struct wgipmask) * (__peer)->num_ipmasks)))
|
||||
|
||||
#define for_each_wgipmask(__peer, __ipmask, __i) for ((__i) = 0, (__ipmask) = (typeof(__ipmask))((uint8_t *)(__peer) + sizeof(struct wgpeer)); \
|
||||
(__i) < (__peer)->num_ipmasks; \
|
||||
++(__i), (__ipmask) = (typeof(__ipmask))((uint8_t *)(__ipmask) + sizeof(struct wgipmask)))
|
||||
|
||||
#endif
|
33
src/pubkey.c
Normal file
33
src/pubkey.c
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <errno.h>
|
||||
#include <resolv.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "curve25519.h"
|
||||
#include "base64.h"
|
||||
|
||||
int pubkey_main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[])
|
||||
{
|
||||
unsigned char private_key[CURVE25519_POINT_SIZE + 1] = { 0 }, public_key[CURVE25519_POINT_SIZE] = { 0 };
|
||||
char private_key_base64[b64_len(CURVE25519_POINT_SIZE)] = { 0 }, public_key_base64[b64_len(CURVE25519_POINT_SIZE)] = { 0 };
|
||||
|
||||
if (fread(private_key_base64, 1, sizeof(private_key_base64) - 1, stdin) != sizeof(private_key_base64) - 1) {
|
||||
errno = EINVAL;
|
||||
perror("fread(private key)");
|
||||
return 1;
|
||||
}
|
||||
if (b64_pton(private_key_base64, private_key, sizeof(private_key)) < 0) {
|
||||
errno = EINVAL;
|
||||
perror("b64");
|
||||
return 1;
|
||||
}
|
||||
curve25519_generate_public(public_key, private_key);
|
||||
if (b64_ntop(public_key, sizeof(public_key), public_key_base64, sizeof(public_key_base64)) < 0) {
|
||||
errno = EINVAL;
|
||||
perror("b64");
|
||||
return 1;
|
||||
}
|
||||
puts(public_key_base64);
|
||||
return 0;
|
||||
}
|
35
src/set.c
Normal file
35
src/set.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "subcommands.h"
|
||||
#include "config.h"
|
||||
#include "kernel.h"
|
||||
|
||||
int set_main(int argc, char *argv[])
|
||||
{
|
||||
struct wgdevice *device = NULL;
|
||||
int ret = 1;
|
||||
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: %s %s <interface> [listen-port <port>] [private-key <file path>] [peer <base64 public key> [remove] [endpoint <ip>:<port>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...] ]...\n", PROG_NAME, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!config_read_cmd(&device, argv + 2, argc - 2))
|
||||
goto cleanup;
|
||||
strncpy(device->interface, argv[1], IFNAMSIZ - 1);
|
||||
device->interface[IFNAMSIZ - 1] = 0;
|
||||
|
||||
if (kernel_set_device(device) != 0) {
|
||||
perror("Unable to set device");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
cleanup:
|
||||
free(device);
|
||||
return ret;
|
||||
}
|
61
src/setconf.c
Normal file
61
src/setconf.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "kernel.h"
|
||||
#include "subcommands.h"
|
||||
|
||||
int setconf_main(int argc, char *argv[])
|
||||
{
|
||||
struct wgdevice *device = NULL;
|
||||
struct config_ctx ctx;
|
||||
FILE *config_input = NULL;
|
||||
char *config_buffer = NULL;
|
||||
size_t config_buffer_len = 0;
|
||||
int ret = 1;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s %s <interface> <configuration filename>\n", PROG_NAME, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
config_input = fopen(argv[2], "r");
|
||||
if (!config_input) {
|
||||
perror("fopen");
|
||||
return 1;
|
||||
}
|
||||
if (!config_read_init(&ctx, &device, !strcmp(argv[0], "addconf"))) {
|
||||
fclose(config_input);
|
||||
return 1;
|
||||
}
|
||||
while (getline(&config_buffer, &config_buffer_len, config_input) >= 0) {
|
||||
if (!config_read_line(&ctx, config_buffer)) {
|
||||
fprintf(stderr, "Configuration parsing error\n");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (!config_read_finish(&ctx) || !device) {
|
||||
fprintf(stderr, "Invalid configuration\n");
|
||||
goto cleanup;
|
||||
}
|
||||
strncpy(device->interface, argv[1], IFNAMSIZ - 1);
|
||||
device->interface[IFNAMSIZ - 1] = 0;
|
||||
|
||||
if (kernel_set_device(device) != 0) {
|
||||
perror("Unable to set device");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
cleanup:
|
||||
if (config_input)
|
||||
fclose(config_input);
|
||||
free(config_buffer);
|
||||
free(device);
|
||||
return ret;
|
||||
}
|
366
src/show.c
Normal file
366
src/show.c
Normal file
|
@ -0,0 +1,366 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <inttypes.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if.h>
|
||||
#include <resolv.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include "kernel.h"
|
||||
#include "subcommands.h"
|
||||
#include "terminal.h"
|
||||
#include "base64.h"
|
||||
#include "../uapi.h"
|
||||
|
||||
static int peer_cmp(const void *first, const void *second)
|
||||
{
|
||||
time_t diff;
|
||||
const struct wgpeer *a = *(const void **)first, *b = *(const void **)second;
|
||||
if (!a->last_handshake_time.tv_sec && !a->last_handshake_time.tv_usec && (b->last_handshake_time.tv_sec || b->last_handshake_time.tv_usec))
|
||||
return 1;
|
||||
if (!b->last_handshake_time.tv_sec && !b->last_handshake_time.tv_usec && (a->last_handshake_time.tv_sec || a->last_handshake_time.tv_usec))
|
||||
return -1;
|
||||
diff = a->last_handshake_time.tv_sec - b->last_handshake_time.tv_sec;
|
||||
if (!diff)
|
||||
diff = a->last_handshake_time.tv_usec - b->last_handshake_time.tv_usec;
|
||||
if (diff < 0)
|
||||
return 1;
|
||||
if (diff > 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sort_peers(struct wgdevice *device)
|
||||
{
|
||||
uint8_t *new_device, *pos;
|
||||
struct wgpeer **peers;
|
||||
struct wgpeer *peer;
|
||||
size_t i, len;
|
||||
|
||||
peers = calloc(device->num_peers, sizeof(struct wgpeer *));
|
||||
if (!peers)
|
||||
return;
|
||||
|
||||
len = sizeof(struct wgdevice);
|
||||
for_each_wgpeer(device, peer, i)
|
||||
len += sizeof(struct wgpeer) + (peer->num_ipmasks * sizeof(struct wgipmask));
|
||||
pos = new_device = malloc(len);
|
||||
if (!new_device) {
|
||||
free(peers);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(pos, device, sizeof(struct wgdevice));
|
||||
pos += sizeof(struct wgdevice);
|
||||
|
||||
for_each_wgpeer(device, peer, i)
|
||||
peers[i] = peer;
|
||||
|
||||
qsort(peers, device->num_peers, sizeof(struct wgpeer *), peer_cmp);
|
||||
for (i = 0; i < device->num_peers; ++i) {
|
||||
len = sizeof(struct wgpeer) + (peers[i]->num_ipmasks * sizeof(struct wgipmask));
|
||||
memcpy(pos, peers[i], len);
|
||||
pos += len;
|
||||
}
|
||||
free(peers);
|
||||
|
||||
memcpy(device, new_device, pos - new_device);
|
||||
free(new_device);
|
||||
}
|
||||
|
||||
static const uint8_t zero[WG_KEY_LEN] = { 0 };
|
||||
|
||||
static char *key(const unsigned char key[WG_KEY_LEN])
|
||||
{
|
||||
static char b64[b64_len(WG_KEY_LEN)];
|
||||
if (!memcmp(key, zero, WG_KEY_LEN))
|
||||
return "(none)";
|
||||
memset(b64, 0, b64_len(WG_KEY_LEN));
|
||||
b64_ntop(key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN));
|
||||
return b64;
|
||||
}
|
||||
|
||||
static char *ip(const struct wgipmask *ip)
|
||||
{
|
||||
static char buf[INET6_ADDRSTRLEN + 1];
|
||||
memset(buf, 0, INET6_ADDRSTRLEN + 1);
|
||||
if (ip->family == AF_INET)
|
||||
inet_ntop(AF_INET, &ip->ip4, buf, INET6_ADDRSTRLEN);
|
||||
else if (ip->family == AF_INET6)
|
||||
inet_ntop(AF_INET6, &ip->ip6, buf, INET6_ADDRSTRLEN);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *endpoint(const struct sockaddr_storage *addr)
|
||||
{
|
||||
char host[4096 + 1];
|
||||
char service[512 + 1];
|
||||
static char buf[sizeof(host) + sizeof(service) + 4];
|
||||
int ret;
|
||||
socklen_t addr_len = 0;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if (addr->ss_family == AF_INET)
|
||||
addr_len = sizeof(struct sockaddr_in);
|
||||
else if (addr->ss_family == AF_INET6)
|
||||
addr_len = sizeof(struct sockaddr_in6);
|
||||
|
||||
ret = getnameinfo((struct sockaddr *)addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST);
|
||||
if (ret)
|
||||
strncpy(buf, gai_strerror(ret), sizeof(buf) - 1);
|
||||
else
|
||||
snprintf(buf, sizeof(buf) - 1, (addr->ss_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *ago(const struct timeval *t)
|
||||
{
|
||||
static char buf[1024];
|
||||
unsigned long long left, years, days, hours, minutes, seconds;
|
||||
size_t offset = 0;
|
||||
|
||||
left = time(NULL) - t->tv_sec;
|
||||
years = left / (365 * 24 * 60 * 60);
|
||||
left = left % (365 * 24 * 60 * 60);
|
||||
days = left / (24 * 60 * 60);
|
||||
left = left % (24 * 60 * 60);
|
||||
hours = left / (60 * 60);
|
||||
left = left % (60 * 60);
|
||||
minutes = left / 60;
|
||||
seconds = left % 60;
|
||||
|
||||
if (years)
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "year%s" TERMINAL_RESET, offset ? ", " : "", years, years == 1 ? "" : "s");
|
||||
if (days)
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "day%s" TERMINAL_RESET, offset ? ", " : "", days, days == 1 ? "" : "s");
|
||||
if (hours)
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "hour%s" TERMINAL_RESET, offset ? ", " : "", hours, hours == 1 ? "" : "s");
|
||||
if (minutes)
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "minute%s" TERMINAL_RESET, offset ? ", " : "", minutes, minutes == 1 ? "" : "s");
|
||||
if (seconds)
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "second%s" TERMINAL_RESET, offset ? ", " : "", seconds, seconds == 1 ? "" : "s");
|
||||
if (offset)
|
||||
snprintf(buf + offset, sizeof(buf) - offset, " ago");
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "Now");
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *bytes(uint64_t b)
|
||||
{
|
||||
static char buf[1024];
|
||||
|
||||
if (b < 1024ULL)
|
||||
snprintf(buf, sizeof(buf), "%u " TERMINAL_FG_CYAN "B" TERMINAL_RESET, (unsigned)b);
|
||||
else if (b < 1024ULL * 1024ULL)
|
||||
snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "KiB" TERMINAL_RESET, (double)b / 1024);
|
||||
else if (b < 1024ULL * 1024ULL * 1024ULL)
|
||||
snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "MiB" TERMINAL_RESET, (double)b / (1024 * 1024));
|
||||
else if (b < 1024ULL * 1024ULL * 1024ULL * 1024ULL)
|
||||
snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "GiB" TERMINAL_RESET, (double)b / (1024 * 1024 * 1024));
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "TiB" TERMINAL_RESET, (double)b / (1024 * 1024 * 1024) / 1024);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *COMMAND_NAME = NULL;
|
||||
static void show_usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } [public-key | private-key | preshared-key | listen-port | peers | endpoints | allowed-ips | latest-handshake | bandwidth]\n", PROG_NAME, COMMAND_NAME);
|
||||
}
|
||||
|
||||
static void pretty_print(struct wgdevice *device)
|
||||
{
|
||||
size_t i, j;
|
||||
struct wgpeer *peer;
|
||||
struct wgipmask *ipmask;
|
||||
|
||||
terminal_printf(TERMINAL_RESET);
|
||||
terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->interface);
|
||||
if (memcmp(device->public_key, zero, WG_KEY_LEN))
|
||||
terminal_printf(" " TERMINAL_BOLD "public key" TERMINAL_RESET ": %s\n", key(device->public_key));
|
||||
if (memcmp(device->private_key, zero, WG_KEY_LEN))
|
||||
terminal_printf(" " TERMINAL_BOLD "private key" TERMINAL_RESET ": %s\n", key(device->private_key));
|
||||
if (memcmp(device->preshared_key, zero, WG_KEY_LEN))
|
||||
terminal_printf(" " TERMINAL_BOLD "pre-shared key" TERMINAL_RESET ": %s\n", key(device->preshared_key));
|
||||
if (device->port)
|
||||
terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->port);
|
||||
if (device->num_peers) {
|
||||
sort_peers(device);
|
||||
terminal_printf("\n");
|
||||
}
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "peer" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET "\n", key(peer->public_key));
|
||||
if (peer->endpoint.ss_family == AF_INET || peer->endpoint.ss_family == AF_INET6)
|
||||
terminal_printf(" " TERMINAL_BOLD "endpoint" TERMINAL_RESET ": %s\n", endpoint(&peer->endpoint));
|
||||
terminal_printf(" " TERMINAL_BOLD "allowed ips" TERMINAL_RESET ": ");
|
||||
if (peer->num_ipmasks) {
|
||||
for_each_wgipmask(peer, ipmask, j)
|
||||
terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? "\n" : ", ");
|
||||
} else
|
||||
terminal_printf("(none)\n");
|
||||
if (peer->last_handshake_time.tv_sec)
|
||||
terminal_printf(" " TERMINAL_BOLD "latest handshake" TERMINAL_RESET ": %s\n", ago(&peer->last_handshake_time));
|
||||
if (peer->rx_bytes || peer->tx_bytes) {
|
||||
terminal_printf(" " TERMINAL_BOLD "bandwidth" TERMINAL_RESET ": ");
|
||||
terminal_printf("%s received, ", bytes(peer->rx_bytes));
|
||||
terminal_printf("%s sent\n", bytes(peer->tx_bytes));
|
||||
}
|
||||
if (i + 1 < device->num_peers)
|
||||
terminal_printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static bool ugly_print(struct wgdevice *device, const char *param, bool with_interface)
|
||||
{
|
||||
size_t i, j;
|
||||
struct wgpeer *peer;
|
||||
struct wgipmask *ipmask;
|
||||
if (!strcmp(param, "public-key")) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\n", key(device->public_key));
|
||||
} else if (!strcmp(param, "private-key")) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\n", key(device->private_key));
|
||||
} else if (!strcmp(param, "preshared-key")) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\n", key(device->preshared_key));
|
||||
} else if (!strcmp(param, "listen-port")) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%u\n", device->port);
|
||||
} else if (!strcmp(param, "endpoints")) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
printf("%s\t", key(peer->public_key));
|
||||
if (peer->endpoint.ss_family == AF_INET || peer->endpoint.ss_family == AF_INET6)
|
||||
printf("%s\n", endpoint(&peer->endpoint));
|
||||
else
|
||||
printf("(none)\n");
|
||||
}
|
||||
} else if (!strcmp(param, "allowed-ips")) {
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\t", key(peer->public_key));
|
||||
if (peer->num_ipmasks) {
|
||||
for_each_wgipmask(peer, ipmask, j)
|
||||
printf("%s/%u%s", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? "\n" : ", ");
|
||||
} else
|
||||
printf("(none)\n");
|
||||
}
|
||||
} else if (!strcmp(param, "latest-handshakes")) {
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\t%llu\n", key(peer->public_key), (unsigned long long)peer->last_handshake_time.tv_sec);
|
||||
}
|
||||
} else if (!strcmp(param, "bandwidth")) {
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\t%" PRIu64 "\t%" PRIu64 "\n", key(peer->public_key), (uint64_t)peer->rx_bytes, (uint64_t)peer->tx_bytes);
|
||||
}
|
||||
} else if (!strcmp(param, "peers")) {
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
if (with_interface)
|
||||
printf("%s\t", device->interface);
|
||||
printf("%s\n", key(peer->public_key));
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Invalid parameter: `%s`\n", param);
|
||||
show_usage();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int show_main(int argc, char *argv[])
|
||||
{
|
||||
int ret = 0;
|
||||
COMMAND_NAME = argv[0];
|
||||
|
||||
if (argc > 3) {
|
||||
show_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc == 1 || !strcmp(argv[1], "all")) {
|
||||
char *interfaces = kernel_get_wireguard_interfaces(), *interface;
|
||||
if (!interfaces) {
|
||||
perror("Unable to get devices");
|
||||
return 1;
|
||||
}
|
||||
interface = interfaces;
|
||||
for (size_t len = 0; (len = strlen(interface)); interface += len + 1) {
|
||||
struct wgdevice *device = NULL;
|
||||
if (kernel_get_device(&device, interface) < 0) {
|
||||
perror("Unable to get device");
|
||||
continue;
|
||||
}
|
||||
if (argc == 3) {
|
||||
if (!ugly_print(device, argv[2], true)) {
|
||||
ret = 1;
|
||||
free(device);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pretty_print(device);
|
||||
if (strlen(interface + len + 1))
|
||||
printf("\n");
|
||||
}
|
||||
free(device);
|
||||
}
|
||||
free(interfaces);
|
||||
} else if (!strcmp(argv[1], "interfaces")) {
|
||||
char *interfaces, *interface;
|
||||
if (argc > 2) {
|
||||
show_usage();
|
||||
return 1;
|
||||
}
|
||||
interfaces = kernel_get_wireguard_interfaces();
|
||||
if (!interfaces) {
|
||||
perror("Unable to get devices");
|
||||
return 1;
|
||||
}
|
||||
interface = interfaces;
|
||||
for (size_t len = 0; (len = strlen(interface)); interface += len + 1)
|
||||
printf("%s%c", interface, strlen(interface + len + 1) ? ' ' : '\n');
|
||||
free(interfaces);
|
||||
} else if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "help")))
|
||||
show_usage();
|
||||
else {
|
||||
struct wgdevice *device = NULL;
|
||||
if (!kernel_has_wireguard_interface(argv[1])) {
|
||||
fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]);
|
||||
show_usage();
|
||||
return 1;
|
||||
}
|
||||
if (kernel_get_device(&device, argv[1]) < 0) {
|
||||
perror("Unable to get device");
|
||||
show_usage();
|
||||
return 1;
|
||||
}
|
||||
if (argc == 3) {
|
||||
if (!ugly_print(device, argv[2], false))
|
||||
ret = 1;
|
||||
} else
|
||||
pretty_print(device);
|
||||
free(device);
|
||||
}
|
||||
return ret;
|
||||
}
|
102
src/showconf.c
Normal file
102
src/showconf.c
Normal file
|
@ -0,0 +1,102 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if.h>
|
||||
#include <resolv.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include "subcommands.h"
|
||||
#include "base64.h"
|
||||
#include "kernel.h"
|
||||
#include "../uapi.h"
|
||||
|
||||
int showconf_main(int argc, char *argv[])
|
||||
{
|
||||
static const uint8_t zero[WG_KEY_LEN] = { 0 };
|
||||
char b64[b64_len(WG_KEY_LEN)] = { 0 };
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
struct wgdevice *device = NULL;
|
||||
struct wgpeer *peer;
|
||||
struct wgipmask *ipmask;
|
||||
size_t i, j;
|
||||
int ret = 1;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s %s <interface>\n", PROG_NAME, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!kernel_has_wireguard_interface(argv[1])) {
|
||||
fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]);
|
||||
fprintf(stderr, "Usage: %s %s <interface>\n", PROG_NAME, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (kernel_get_device(&device, argv[1])) {
|
||||
perror("Unable to get device");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("[Interface]\n");
|
||||
if (device->port)
|
||||
printf("ListenPort = %d\n", device->port);
|
||||
if (memcmp(device->private_key, zero, WG_KEY_LEN)) {
|
||||
b64_ntop(device->private_key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN));
|
||||
printf("PrivateKey = %s\n", b64);
|
||||
}
|
||||
if (memcmp(device->preshared_key, zero, WG_KEY_LEN)) {
|
||||
b64_ntop(device->preshared_key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN));
|
||||
printf("PresharedKey = %s\n", b64);
|
||||
}
|
||||
printf("\n");
|
||||
for_each_wgpeer(device, peer, i) {
|
||||
b64_ntop(peer->public_key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN));
|
||||
printf("[Peer]\nPublicKey = %s\n", b64);
|
||||
if (peer->num_ipmasks)
|
||||
printf("AllowedIPs = ");
|
||||
for_each_wgipmask(peer, ipmask, j) {
|
||||
if (ipmask->family == AF_INET) {
|
||||
if (!inet_ntop(AF_INET, &ipmask->ip4, ip, INET6_ADDRSTRLEN))
|
||||
continue;
|
||||
} else if (ipmask->family == AF_INET6) {
|
||||
if (!inet_ntop(AF_INET6, &ipmask->ip6, ip, INET6_ADDRSTRLEN))
|
||||
continue;
|
||||
} else
|
||||
continue;
|
||||
printf("%s/%d", ip, ipmask->cidr);
|
||||
if (j + 1 < (size_t)peer->num_ipmasks)
|
||||
printf(", ");
|
||||
}
|
||||
if (peer->num_ipmasks)
|
||||
printf("\n");
|
||||
|
||||
if (peer->endpoint.ss_family == AF_INET || peer->endpoint.ss_family == AF_INET6) {
|
||||
char host[4096 + 1];
|
||||
char service[512 + 1];
|
||||
static char buf[sizeof(host) + sizeof(service) + 4];
|
||||
socklen_t addr_len = 0;
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if (peer->endpoint.ss_family == AF_INET)
|
||||
addr_len = sizeof(struct sockaddr_in);
|
||||
else if (peer->endpoint.ss_family == AF_INET6)
|
||||
addr_len = sizeof(struct sockaddr_in6);
|
||||
if (!getnameinfo((struct sockaddr *)&peer->endpoint, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST)) {
|
||||
snprintf(buf, sizeof(buf) - 1, (peer->endpoint.ss_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service);
|
||||
printf("Endpoint = %s\n", buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 < device->num_peers)
|
||||
printf("\n");
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
cleanup:
|
||||
free(device);
|
||||
return ret;
|
||||
}
|
14
src/subcommands.h
Normal file
14
src/subcommands.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#ifndef SUBCOMMANDS_H
|
||||
#define SUBCOMMANDS_H
|
||||
|
||||
extern const char *PROG_NAME;
|
||||
int show_main(int argc, char *argv[]);
|
||||
int showconf_main(int argc, char *argv[]);
|
||||
int set_main(int argc, char *argv[]);
|
||||
int setconf_main(int argc, char *argv[]);
|
||||
int genkey_main(int argc, char *argv[]);
|
||||
int pubkey_main(int argc, char *argv[]);
|
||||
|
||||
#endif
|
79
src/terminal.c
Normal file
79
src/terminal.c
Normal file
|
@ -0,0 +1,79 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool color_mode(FILE *file)
|
||||
{
|
||||
static int mode = -1;
|
||||
char *var;
|
||||
if (mode != -1)
|
||||
return mode;
|
||||
var = getenv("WG_COLOR_MODE");
|
||||
if (var && !strcmp(var, "always"))
|
||||
mode = true;
|
||||
else if (var && !strcmp(var, "never"))
|
||||
mode = false;
|
||||
else
|
||||
return isatty(fileno(file));
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void filter_ansi(FILE *file, const char *fmt, va_list args)
|
||||
{
|
||||
char *str = NULL;
|
||||
size_t len, i, j;
|
||||
|
||||
if (color_mode(file)) {
|
||||
vfprintf(file, fmt, args);
|
||||
return;
|
||||
}
|
||||
|
||||
len = vasprintf(&str, fmt, args);
|
||||
|
||||
if (len >= 2) {
|
||||
for (i = 0; i < len - 2; ++i) {
|
||||
if (str[i] == '\x1b' && str[i + 1] == '[') {
|
||||
str[i] = str[i + 1] = '\0';
|
||||
for (j = i + 2; j < len; ++j) {
|
||||
if (isalpha(str[j]))
|
||||
break;
|
||||
str[j] = '\0';
|
||||
}
|
||||
str[j] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < len; i = j) {
|
||||
fputs(&str[i], file);
|
||||
for (j = i + strlen(&str[i]); j < len; ++j) {
|
||||
if (str[j] != '\0')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(str);
|
||||
}
|
||||
|
||||
void terminal_printf(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
filter_ansi(stdout, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void terminal_fprintf(FILE *file, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
filter_ansi(file, fmt, args);
|
||||
va_end(args);
|
||||
}
|
49
src/terminal.h
Normal file
49
src/terminal.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#ifndef TERMINAL_H
|
||||
#define TERMINAL_H
|
||||
|
||||
#define TERMINAL_FG_BLACK "\x1b[30m"
|
||||
#define TERMINAL_FG_RED "\x1b[31m"
|
||||
#define TERMINAL_FG_GREEN "\x1b[32m"
|
||||
#define TERMINAL_FG_YELLOW "\x1b[33m"
|
||||
#define TERMINAL_FG_BLUE "\x1b[34m"
|
||||
#define TERMINAL_FG_MAGENTA "\x1b[35m"
|
||||
#define TERMINAL_FG_CYAN "\x1b[36m"
|
||||
#define TERMINAL_FG_WHITE "\x1b[37m"
|
||||
#define TERMINAL_FG_DEFAULT "\x1b[39m"
|
||||
|
||||
#define TERMINAL_BG_BLACK "\x1b[40m"
|
||||
#define TERMINAL_BG_RED "\x1b[41m"
|
||||
#define TERMINAL_BG_GREEN "\x1b[42m"
|
||||
#define TERMINAL_BG_YELLOW "\x1b[43m"
|
||||
#define TERMINAL_BG_BLUE "\x1b[44m"
|
||||
#define TERMINAL_BG_MAGENTA "\x1b[45m"
|
||||
#define TERMINAL_BG_CYAN "\x1b[46m"
|
||||
#define TERMINAL_BG_WHITE "\x1b[47m"
|
||||
#define TERMINAL_BG_DEFAULT "\x1b[49m"
|
||||
|
||||
#define TERMINAL_BOLD "\x1b[1m"
|
||||
#define TERMINAL_NO_BOLD "\x1b[22m"
|
||||
#define TERMINAL_UNDERLINE "\x1b[4m"
|
||||
#define TERMINAL_NO_UNDERLINE "\x1b[24m"
|
||||
|
||||
#define TERMINAL_RESET "\x1b[0m"
|
||||
|
||||
#define TERMINAL_SAVE_CURSOR "\x1b[s"
|
||||
#define TERMINAL_RESTORE_CURSOR "\x1b[u"
|
||||
#define TERMINAL_UP_CURSOR(l) "\x1b[" #l "A"
|
||||
#define TERMINAL_DOWN_CURSOR(l) "\x1b[" #l "B"
|
||||
#define TERMINAL_RIGHT_CURSOR(c) "\x1b[" #c "C"
|
||||
#define TERMINAL_LEFT_CURSOR(c) "\x1b[" #c "D"
|
||||
#define TERMINAL_CLEAR_DOWN "\x1b[0J"
|
||||
#define TERMINAL_CLEAR_UP "\x1b[1J"
|
||||
#define TERMINAL_CLEAR_RIGHT "\x1b[0K"
|
||||
#define TERMINAL_CLEAR_LEFT "\x1b[1K"
|
||||
#define TERMINAL_CLEAR_LINE "\x1b[2K"
|
||||
#define TERMINAL_CLEAR_ALL "\x1b[2J"
|
||||
|
||||
void terminal_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void terminal_fprintf(FILE *file, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
#endif
|
194
src/wg.8
Normal file
194
src/wg.8
Normal file
|
@ -0,0 +1,194 @@
|
|||
.TH WG 8 "2015 August 13" ZX2C4 "WireGuard"
|
||||
|
||||
.SH NAME
|
||||
wg - set and retrieve configuration of WireGuard interfaces
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B wg
|
||||
[
|
||||
.I COMMAND
|
||||
] [
|
||||
.I OPTIONS
|
||||
]... [
|
||||
.I ARGS
|
||||
]...
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
.B wg
|
||||
is the configuration utility for getting and setting the configuration of
|
||||
WireGuard tunnel interfaces. The interfaces themselves can be added and removed
|
||||
using
|
||||
.BR ip-link (8)
|
||||
and their IP addresses and routing tables can be set using
|
||||
.BR ip-address (8)
|
||||
and
|
||||
.BR ip-route (8).
|
||||
The
|
||||
.B wg
|
||||
utility provides a series of sub-commands for changing WireGuard-specific
|
||||
aspects of WireGuard interfaces.
|
||||
|
||||
If no COMMAND is specified, COMMAND defaults to
|
||||
.BR show .
|
||||
Sub-commands that take an INTERFACE must be passed a WireGuard interface.
|
||||
|
||||
.SH COMMANDS
|
||||
|
||||
.TP
|
||||
\fBshow\fP { \fI<interface>\fP | \fIall\fP | \fIinterfaces\fP } [\fIpublic-key\fP | \fIprivate-key\fP | \fIpreshared-key\fP | \fIlisten-port\fP | \fIpeers\fP | \fIendpoints\fP | \fIallowed-ips\fP | \fIlatest-handshake\fP | \fIbandwidth\fP]
|
||||
Shows current WireGuard configuration of specified \fI<interface>\fP.
|
||||
If no \fI<interface>\fP is specified, \fI<interface>\fP defaults to \fIall\fP.
|
||||
If \fIinterfaces\fP is specified, prints a list of all WireGuard interfaces,
|
||||
one per line, and quit. If no options are given after the interface
|
||||
specification, then prints a list of all attributes in a visually pleasing way
|
||||
meant for the terminal. Otherwise, prints specified information grouped by
|
||||
newlines and tabs, meant to be used in scripts.
|
||||
.TP
|
||||
\fBshowconf\fP \fI<interface>\fP
|
||||
Shows the current configuration of \fI<interface>\fP in the format described
|
||||
by \fICONFIGURATION FILE FORMAT\fP below.
|
||||
.TP
|
||||
\fBset\fP \fI<interface>\fP [\fIlisten-port\fP \fI<port>\fP] [\fIprivate-key\fP \fI<file-path>\fP] [\fIpreshared-key\fP \fI<file-path>\fP] [\fIpeer\fP \fI<base64-public-key>\fP [\fIremove\fP] [\fIendpoint\fP \fI<ip>:<port>\fP] [\fIallowed-ips\fP \fI<ip1>/<cidr1>\fP[,\fI<ip2>/<cidr2>\fP]...] ]...
|
||||
Sets configuration values for the specified \fI<interface>\fP. Multiple
|
||||
\fIpeer\fPs may be specified, and if the \fIremove\fP argument is given
|
||||
for a peer, that peer is removed, not configured. If \fIlisten-port\fP
|
||||
is not specified, the port will be automatically generated when the
|
||||
interface comes up. Both \fIprivate-key\fP and \fIpreshared-key\fP must
|
||||
be a files, for security reasons, but if you're using
|
||||
.BR bash (1),
|
||||
you may safely pass in a string by specifying as \fIprivate-key\fP or
|
||||
\fIpreshared-key\fP the expression: <(echo PRIVATEKEYSTRING). If
|
||||
\fI/dev/null\fP is specified as the filename for either \fIprivate-key\fP or
|
||||
\fIpreshared-key\fP, the key is removed from the device. The use of
|
||||
\fIpreshared-key\fP is optional, and may be omitted; it adds an additional
|
||||
layer of symmetric-key cryptography to be mixed into the already existing
|
||||
public-key cryptography, for post-quantum resistance. If \fIallowed-ips\fP
|
||||
is specified, but the value is the empty string, all allowed ips are removed
|
||||
from the peer.
|
||||
.TP
|
||||
\fBsetconf\fP \fI<interface>\fP \fI<configuration-filename>\fP
|
||||
Sets the current configuration of \fI<interface>\fP to the contents of
|
||||
\fI<configuration-filename>\fP, which must be in the format described
|
||||
by \fICONFIGURATION FILE FORMAT\fP below.
|
||||
.TP
|
||||
\fBaddconf\fP \fI<interface>\fP \fI<configuration-filename>\fP
|
||||
Appends the contents of \fI<configuration-filename>\fP, which must
|
||||
be in the format described by \fICONFIGURATION FILE FORMAT\fP below,
|
||||
to the current configuration of \fI<interface>\fP.
|
||||
.TP
|
||||
\fBgenkey\fP
|
||||
Generates a random \fIprivate\fP key in base64 and prints it to
|
||||
standard output.
|
||||
.TP
|
||||
\fBgenpsk\fP
|
||||
Generates a random \fIpreshared\fP key in base64 and prints it to
|
||||
standard output.
|
||||
.TP
|
||||
\fBpubkey\fP
|
||||
Calculates a \fIpublic\fP key and prints it in base64 to standard
|
||||
output from a corresponding \fIprivate\fP key (generated with
|
||||
\fIgenkey\fP) given in base64 on standard input.
|
||||
|
||||
A private key and a corresponding public key may be generated at once by calling:
|
||||
.br
|
||||
$ umask 077
|
||||
.br
|
||||
$ wg genkey | tee private.key | wg pubkey > public.key
|
||||
.TP
|
||||
\fBhelp\fP
|
||||
Show usage message.
|
||||
|
||||
.SH CONFIGURATION FILE FORMAT
|
||||
The configuration file format is based on \fIINI\fP. There are two top level sections
|
||||
-- \fIInterface\fP and \fIPeer\fP. Multiple \fIPeer\fP sections may be specified, but
|
||||
only one \fIInterface\fP section may be specified.
|
||||
|
||||
.P
|
||||
The \fIInterface\fP section contains two fields:
|
||||
.IP \(bu
|
||||
PrivateKey \(em a base64 private key generated by \fIwg genkey\fP. Required.
|
||||
.IP \(bu
|
||||
PresharedKey \(em a base64 preshared key generated by \fIwg genpsk\fP. Optional,
|
||||
and may be omitted. This option adds an additional layer of symmetric-key
|
||||
cryptography to be mixed into the already existing public-key cryptography,
|
||||
for post-quantum resistance.
|
||||
.IP \(bu
|
||||
ListenPort \(em a 16-bit port for listening. Optional; if not specified,
|
||||
automatically generated based on interface name.
|
||||
.P
|
||||
The \fIPeer\fP sections contain three fields each:
|
||||
.IP \(bu
|
||||
PublicKey \(em a base64 public key calculated by \fIwg pubkey\fP from a
|
||||
private key, and usually transmitted out of band to the author of the
|
||||
configuration file. Required.
|
||||
.IP \(bu
|
||||
AllowedIPs \(em a comma-separated list of IP (v4 or v6) addresses with
|
||||
CIDR masks. The catch-all \fI0.0.0.0/0\fP may be specified for matching
|
||||
all IPv4 addresses, and \fI::/0\fP may be specified for matching all
|
||||
IPv6 addresses. Required.
|
||||
.IP \(bu
|
||||
Endpoint \(em an endpoint IP or hostname, followed by a comma, and then a
|
||||
port number. Optional.
|
||||
|
||||
.SH CONFIGURATION FILE FORMAT EXAMPLE
|
||||
This example may be used as a model for writing configuration files.
|
||||
Note that not all keys are required.
|
||||
|
||||
[Interface]
|
||||
.br
|
||||
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
|
||||
.br
|
||||
ListenPort = 41414
|
||||
.br
|
||||
|
||||
.br
|
||||
[Peer]
|
||||
.br
|
||||
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
|
||||
.br
|
||||
Endpoint = 192.95.5.67:1234
|
||||
.br
|
||||
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
|
||||
.br
|
||||
|
||||
.br
|
||||
[Peer]
|
||||
.br
|
||||
PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
|
||||
.br
|
||||
Endpoint = [2607:5300:60:6b0::c05f:543]:2468
|
||||
.br
|
||||
AllowedIPs = 10.192.122.4/32, 192.168.0.0/16
|
||||
.br
|
||||
|
||||
.br
|
||||
[Peer]
|
||||
.br
|
||||
PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
|
||||
.br
|
||||
Endpoint = test.wireguard.io:18981
|
||||
.br
|
||||
AllowedIPs = 10.10.10.230/32
|
||||
|
||||
.SH ENVIRONMENT VARIABLES
|
||||
.TP
|
||||
.I WG_COLOR_MODE
|
||||
If set to \fIalways\fP, always print ANSI colorized output. If set to \fInever\fP, never print ANSI colorized output. If set to \fIauto\fP, something invalid, or unset, then print ANSI colorized output only when writing to a TTY.
|
||||
|
||||
.SH SEE ALSO
|
||||
.BR ip (8),
|
||||
.BR ip-link (8),
|
||||
.BR ip-address (8),
|
||||
.BR ip-route (8).
|
||||
|
||||
.SH AUTHOR
|
||||
.B wg
|
||||
was written by
|
||||
.MT Jason@zx2c4.com
|
||||
Jason A. Donenfeld
|
||||
.ME .
|
||||
For updates and more information, a project page is available on the
|
||||
.UR http://\:www.wireguard.io/
|
||||
World Wide Web
|
||||
.UE .
|
66
src/wg.c
Normal file
66
src/wg.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "subcommands.h"
|
||||
|
||||
const char *PROG_NAME;
|
||||
|
||||
static const struct {
|
||||
const char *subcommand;
|
||||
int (*function)(int, char**);
|
||||
const char *description;
|
||||
} subcommands[] = {
|
||||
{ "show", show_main, "Shows the current configuration and device information" },
|
||||
{ "showconf", showconf_main, "Shows the current configuration of a given WireGuard interface, for use with `setconf`" },
|
||||
{ "set", set_main, "Change the current configuration, add peers, remove peers, or change peers" },
|
||||
{ "setconf", setconf_main, "Applies a configuration file to a WireGuard interface" },
|
||||
{ "addconf", setconf_main, "Appends a configuration file to a WireGuard interface" },
|
||||
{ "genkey", genkey_main, "Generates a new private key and writes it to stdout" },
|
||||
{ "genpsk", genkey_main, "Generates a new pre-shared key and writes it to stdout" },
|
||||
{ "pubkey", pubkey_main, "Reads a private key from stdin and writes a public key to stdout" }
|
||||
};
|
||||
|
||||
static void show_usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <cmd> [<args>]\n\n", PROG_NAME);
|
||||
fprintf(stderr, "Available subcommands:\n");
|
||||
for (size_t i = 0; i < sizeof(subcommands) / sizeof(subcommands[0]); ++i)
|
||||
fprintf(stderr, " %s: %s\n", subcommands[i].subcommand, subcommands[i].description);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *tmp = NULL;
|
||||
PROG_NAME = argv[0];
|
||||
|
||||
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "help"))) {
|
||||
show_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc == 1) {
|
||||
char *new_argv[] = { "show", NULL };
|
||||
return show_main(1, new_argv);
|
||||
}
|
||||
|
||||
findsubcommand:
|
||||
for (size_t i = 0; i < sizeof(subcommands) / sizeof(subcommands[0]); ++i) {
|
||||
if (!strcmp(argv[1], subcommands[i].subcommand))
|
||||
return subcommands[i].function(argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
/* Crude way of supporting "wg wg0 show..." */
|
||||
if (!tmp && argc >= 3) {
|
||||
tmp = argv[1];
|
||||
argv[1] = argv[2];
|
||||
argv[2] = tmp;
|
||||
goto findsubcommand;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Invalid subcommand: `%s`\n", argv[1]);
|
||||
show_usage();
|
||||
return 1;
|
||||
}
|
Loading…
Reference in a new issue