Initial commit

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2015-06-05 15:58:00 +02:00
commit 8132305e54
43 changed files with 4379 additions and 0 deletions

10
.gitignore vendored Normal file
View 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
View 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
View file

@ -0,0 +1,10 @@
# WireGuard &mdash; 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).

View 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

View 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

View 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

View 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-----

View 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

View 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

View 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()
}

View file

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View 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

View 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

View 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

View file

@ -0,0 +1,2 @@
Cargo.lock
target/

View 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 = "*"

View 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();
}

View 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;
}

View 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;
}

View 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

View 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
View 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
View file

@ -0,0 +1,3 @@
*.d
*.o
wg

26
src/Makefile Normal file
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

22
src/curve25519.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}