-GoVPN is simple secure free software virtual private network daemon,
-aimed to be reviewable, secure, DPI/censorship-resistant, written on Go.
+GoVPN is simple free software virtual private network daemon, aimed to
+be reviewable, secure, DPI/censorship-resistant, written on Go.
-It uses fast strong password authenticated key agreement protocol with
-augmented mutual peers authentication (PAKE DH A-EKE). Encrypted,
-authenticated data transport that hides message's length and timestamps.
-Perfect forward secrecy property. Resistance to: offline dictionary
-attacks, replay attacks, client's passwords compromising and dictionary
-attacks on the server side. Built-in heartbeating, rehandshaking,
-real-time statistics. Ability to work through UDP, TCP and HTTP proxies.
-IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support.
+It uses fast strong passphrase authenticated key agreement protocol with
+augmented zero-knowledge mutual peers authentication (PAKE DH A-EKE).
+Encrypted, authenticated data transport that hides message's length and
+timestamps. Perfect forward secrecy property. Resistance to: offline
+dictionary attacks, replay attacks, client's passphrases compromising
+and dictionary attacks on the server side. Built-in heartbeating,
+rehandshaking, real-time statistics. Ability to work through UDP, TCP
+and HTTP proxies. IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support.
GoVPN is free software: see the file COPYING for copying conditions.
-Home page: http://www.cypherpunks.ru/govpn/
+Home page: http://govpn.info/ -> http://www.cypherpunks.ru/govpn/
also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/
-Please send all questions, bug reports and patches to
-govpn-devel@lists.cypherpunks.ru mailing list.
-Also it is used for announcements. Either visit
-https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel for
-subscription and archive access information, or send email with the
-subject "subscribe" to govpn-devel-request@lists.cypherpunks.ru.
+Please send questions regarding the use of GoVPN, bug reports and
+patches to govpn-devel mailing list:
+https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel
Development Git source code repository currently is located here:
http://git.cypherpunks.ru/cgit.cgi/govpn.git/
-For futher information please read either doc/govpn.info or doc/govpn.texi.
+For further information please read either doc/govpn.info or doc/govpn.texi.
--- /dev/null
+GoVPN это простой демон виртуальных частных сетей, код которого нацелен
+на лёгкость чтения и анализа, безопасность, устойчивость к DPI/цензуре,
+написан на Go и является свободным программным обеспечением.
+
+Он использует быстрый сильный аутентифицируемый по парольной фразе
+несбалансированный протокол согласования ключей с двусторонней
+аутентификацией сторон (PAKE DH A-EKE). Зашифрованный, аутентифицируемый
+транспортный протокол передачи данных, скрывающий длины сообщений и их
+временные характеристики. Свойство совершенной прямой секретности.
+Устойчивость к: внесетевым (offline) атакам по словарю, атакам
+повторного воспроизведения (replay), компрометации клиентских парольных
+фраз на стороне сервера. Встроенные функции сердцебиения (heartbeat),
+пересогласования ключей, статистика реального времени. Возможность
+работы поверх UDP, TCP и HTTP прокси. Совместимость с IPv4 и IPv6.
+Поддержка GNU/Linux и FreeBSD.
+
+GoVPN это свободное программное обеспечением: условия распространения
+находятся в файле COPYING.
+
+Домашняя страница: http://govpn.info/ -> http://www.cypherpunks.ru/govpn/
+также доступна как скрытый сервис Tor: http://vabu56j2ep2rwv3b.onion/govpn/
+
+Пожалуйста все вопросы касающиеся использования GoVPN, отчёты об ошибках
+и патчи отправляйте в govpn-devel почтовую рассылку:
+https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/
+
+Исходный код для разработчика находится в Git репозитории:
+http://git.cypherpunks.ru/cgit.cgi/govpn.git/
+
+За большей информацией пожалуйста прочтите или doc/govpn.info или
+doc/govpn.texi.
cp -f utils/newclient.sh utils/storekey.sh $(SHAREDIR)
chmod 755 $(SHAREDIR)/newclient.sh $(SHAREDIR)/storekey.sh
mkdir -p $(DOCDIR)
- cp -f -L AUTHORS INSTALL NEWS README THANKS $(DOCDIR)
- chmod 644 $(DOCDIR)/AUTHORS $(DOCDIR)/INSTALL $(DOCDIR)/NEWS $(DOCDIR)/README $(DOCDIR)/THANKS
+ cp -f -L AUTHORS INSTALL NEWS README README.RU THANKS $(DOCDIR)
+ chmod 644 $(DOCDIR)/*
install-strip: install
strip $(BINDIR)/govpn-client $(BINDIR)/govpn-server $(BINDIR)/govpn-verifier
--- /dev/null
+@node About RU
+@unnumbered Подробнее о демоне GoVPN
+
+GoVPN это простой демон виртуальных частных сетей, код которого нацелен
+на лёгкость чтения и анализа, безопасность, устойчивость к DPI/цензуре.
+
+@itemize @bullet
+@item
+Свободное программное обеспечение, копилефт: лицензировано под условиями
+@url{https://www.gnu.org/licenses/gpl-3.0.ru.html, GPLv3+}.
+@item
+Быстрый сильный аутентифицируемый по парольной фразе несбалансированный
+протокол согласования ключей с двусторонней аутентификацией сторон и
+нулевым неразглашением (PAKE DH A-EKE (Diffie-Hellman Augmented
+Encrypted Key Exchange)).
+@item
+Несбалансированные аутентификационные токены устойчивые к внесетевым
+(offline) атакам по словарю. Злоумышленник не может замаскироваться под
+клиента даже скомпрометировав базу данных токенов сервера.
+@item
+Зашифрованный и аутентифицируемый транспортный протокол передачи данных
+с 128-бит порогом безопасности и современной криптографией.
+@item
+Цензуроустойчивые сообщения транспорта и рукопожатия: неотличимые от
+шума с опциональным скрытием размеров сообщений.
+@item
+Свойство совершенной прямой секретности (perfect forward secrecy).
+@item
+Защита от атак повторного воспроизведения (replay) (используя
+одноразовые MAC).
+@item
+Встроенные функции пересогласования ключей (ротация сессионных ключей) и
+сердцебиения (heartbeat).
+@item
+Возможность скрывать размеры пакетов путём зашумления данных.
+@item
+Возможность скрывать временные характеристики полезной нагрузки путём
+постоянного по скорости трафика.
+@item
+Совместимость с @url{http://egd.sourceforge.net/, EGD} (демон сборки
+энтропии) генераторами псевдослучайных чисел.
+@item
+Поддержка нескольких клиентов одновременно с специфичной для каждого
+конфигурацией. Клиенты имеют заранее установленный идентификатор,
+невидимый третьим лицам (они анонимны для них).
+@item
+Использует TAP низлежащие сетевые интерфейсы.
+@item
+Может работать поверх UDP, TCP и HTTP прокси для доступа к серверу.
+@item
+Полностью IPv4 и IPv6 совместимый.
+@item
+Опциональный встроенный HTTP-сервер для получения статистики о
+подключённых клиентах в режиме реального времени в JSON формате.
+@item
+Написан на языке Go с простым кодом, ориентированным на лёгкость чтения
+и анализа.
+@item
+Поддержка GNU/Linux и FreeBSD.
+@end itemize
--- /dev/null
+GoVPN is simple free software virtual private network daemon,
+aimed to be reviewable, secure and
+@url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}/censorship-resistant.
+
+@itemize @bullet
+@item
+Copylefted free software: licenced under
+@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}.
+@item
+Fast strong @ref{PAKE, passphrase authenticated} augmented
+@ref{Handshake, key agreement protocol} with zero-knowledge mutual peers
+authentication (PAKE DH A-EKE (Diffie-Hellman Augmented Encrypted Key
+Exchange)).
+@item
+@ref{Verifier structure, Augmented authentication tokens} resistant to
+offline dictionary attacks. An attacker can not masquerade a client
+even with server passphrase verifiers compromising.
+@item
+Encrypted and authenticated @ref{Transport, payload transport}
+with 128-bit @ref{Developer, security margin} state-of-the-art
+cryptography.
+@item
+Censorship resistant handshake and transport messages: fully
+indistinguishable from the noise with optionally hidden packets lengths.
+@item
+@url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy}
+property.
+@item
+Replay attack protection (using one-time MACs).
+@item
+Built-in rehandshake (session key rotation) and heartbeat features.
+@item
+Ability to hide packets length with the @ref{Noise, noise} data.
+@item
+Ability to hide payload timestamps with @ref{CPR, constant packet rate}
+traffic.
+@item
+Compatible with @url{http://egd.sourceforge.net/, EGD} (entropy
+gathering daemon) PRNGs.
+@item
+Several simultaneous clients support with per-client configuration
+options. Clients have pre-established @ref{Identity, identity} invisible
+for third-parties (they are anonymous).
+@item
+Uses @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP}
+underlying network interfaces.
+@item
+Can use @ref{Network, UDP and TCP} or HTTP @ref{Proxy, proxies}
+for accessing the server.
+@item
+Fully IPv4 and IPv6 compatible.
+@item
+Optional built-in HTTP-server for retrieving real-time
+@ref{Stats, statistics} information about known connected peers in
+@url{http://json.org/, JSON} format.
+@item
+Written on @url{http://golang.org/, Go} programming language with
+simple code that can be read and reviewed.
+@item
+@url{https://www.gnu.org/, GNU}/Linux and
+@url{http://www.freebsd.org/, FreeBSD} support.
+@end itemize
-@node Client part
+@node Client
@section Client part
Except for common @code{-mtu}, @code{-stats}, @code{-egd}
@table @code
@item -proto
-@ref{Network transport} to use. Can be either @emph{udp} or @emph{tcp}.
+@ref{Network, network protocol} to use. Can be either @emph{udp}
+(default) or @emph{tcp}.
@item -proxy
Use specified @emph{host:port} @ref{Proxy} server for accessing remote
Enable @ref{Noise}.
@item -cpr
-Enable @ref{CPR} in KiB/sec.
+Set @ref{CPR} in KiB/sec.
@item -up
Optional path to script that will be executed after connection is
@node Contacts
@unnumbered Contacts
-Please send all questions, bug reports and patches to to
-@email{govpn-devel@@lists.cypherpunks.ru} mailing list.
-Announcements go to this mailing list too.
+Please send questions regarding the use of GoVPN, bug reports and patches to
+@url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel, govpn-devel}
+mailing list. Announcements also go to this mailing list.
-Either visit @url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel}
-for information about subscription options and archived messages access, or
-send email with the subject @code{subscribe} to
-@email{govpn-devel-request@@lists.cypherpunks.ru}.
-
-Official website is @url{http://www.cypherpunks.ru/govpn/}, also available
-as @url{https://www.torproject.org/, Tor} hidden service:
+Official website is @url{http://www.cypherpunks.ru/govpn/}
+(with @url{http://govpn.info/} alias), also available as
+@url{https://www.torproject.org/, Tor} hidden service:
@url{http://vabu56j2ep2rwv3b.onion/govpn/}.
This mode is turned by @code{-cpr} option, where you specify desired
outgoing traffic rate in KiB/sec (kibibytes per second). This option also
-forces using of the @ref{Noise}! It is turned off by default.
+@strong{forces} using of the @ref{Noise}! It is turned off by default.
-@node Developer manual
+@node Developer
@unnumbered Developer manual
-Pay attention how to get @ref{Development source code}.
+Pay attention how to get @ref{Sources, development source code}.
@table @asis
@item Nonce and identity encryption
@url{https://en.wikipedia.org/wiki/PBKDF2, PBKDF2} based on
@url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}.
@item Packet overhead
-26 bytes per packet. Two more bytes in TCP mode.
+26 bytes per packet.
@item Handshake overhead
4 UDP (2 from client, 2 from server) packets (round-trips for TCP),
-264 bytes total payload (8 bytes more in TCP mode).
+264 bytes total payload.
@item Entropy required
832 bits in average on client, 832 bits in average on server side per
handshake.
@menu
* Verifier structure::
-* Transport protocol::
-* Handshake protocol::
+* Transport protocol: Transport.
+* Handshake protocol: Handshake.
@end menu
@include verifierstruct.texi
-@node Prepared tarballs
+@node Tarballs
@section Prepared tarballs
You can obtain releases source code prepared tarballs from the links below:
@multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
@headitem Version @tab Size @tab Tarball @tab SHA256 checksum
+@item 3.5 @tab 179 KiB
+@tab @url{download/govpn-3.5.tar.xz, link} @url{download/govpn-3.5.tar.xz.sig, sign}
+@tab @code{6b60c2cd4a8b4b2c893e52d3366510678704fd68a02a0ea24cb112bd753ea54b}
+
@item 3.4 @tab 175 KiB
@tab @url{download/govpn-3.4.tar.xz, link} @url{download/govpn-3.4.tar.xz.sig, sign}
@tab @code{266612a7f8faa6ceb2955ed611c0c21872776306f4eaad5b785145bbb0390c82}
-@node Example usage
+@node Example
@section Example usage
Let's assume that there is some insecure link between your computer and
@end itemize
@strong{Install}. At first you must @ref{Installation, install} this
-software: download, check the signature, compile.
+software: download, @ref{Integrity, check the signature}, compile.
-@strong{Prepare the server}. Create the new client, named (for example)
-"Alice":
+@strong{Prepare the client}. Generate client's identity and verifier for
+Alice as an example:
+@verbatim
+client% ./utils/newclient.sh Alice
+Enter passphrase:
+Your id is: 7012df29deee2170594119df5091d4a2
-@example
-server% ./utils/newclient.sh Alice
-Place verifier to peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier
-@end example
-
-"6d4ac605ce8dc37c2f0bf21cb542a713" -- is the new client's identity.
-
-@strong{Prepare the client}. Generate @ref{Verifier} for known client
-identity:
+Place the following JSON configuration entry on the server's side:
-@example
-client% ./utils/storekey.sh /tmp/passphrase
-Enter passphrase:[my secure passphrase is here]
-client% govpn-verifier -id 6d4ac605ce8dc37c2f0bf21cb542a713 -key /tmp/passphrase
-562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55
-client% rm /tmp/passphrase
-@end example
+ "7012df29deee2170594119df5091d4a2": {
+ "name": "Alice",
+ "up": "/path/to/up.sh",
+ "verifier": "fb43255ca3fe5bd884e364e5eae0cd37ad14774930a027fd38d8938fd0b57425"
+ }
-"562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55" --
-this is verifier itself.
+Verifier was generated with:
-@strong{Save verifier on server}.
+ ./utils/storekey.sh /tmp/passphrase
+ govpn-verifier -id 7012df29deee2170594119df5091d4a2 -key /tmp/passphrase
+@end verbatim
-@example
-server% cat > peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier <<EOF
-562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55
-EOF
-@end example
+@strong{Prepare the server}. Add this entry to @code{peers.json}
+configuration file.
@strong{Prepare network on GNU/Linux IPv4 server}:
@example
-server% echo "echo tap10" >> peers/6d4ac605ce8dc37c2f0bf21cb542a713/up.sh
+server% umask 077
+server% echo "#!/bin/sh" > /path/to/up.sh
+server% echo "echo tap10" >> /path/to/up.sh
server% ip addr add 192.168.0.1/24 dev wlan0
server% tunctl -t tap10
server% ip link set mtu 1432 dev tap10
@example
client% govpn-client \
-key key.txt \
- -id 6d4ac605ce8dc37c2f0bf21cb542a713 \
+ -id 906e34b98750c4f686d6c5489508763c \
-iface tap10 \
-remote 192.168.0.1:1194 \
-mtu 1472
client% route -6 add default fc00::1
client% govpn-client \
-key key.txt \
- -id 6d4ac605ce8dc37c2f0bf21cb542a713 \
+ -id 906e34b98750c4f686d6c5489508763c \
-iface tap10 \
-remote "[fe80::1%me0]":1194
@end example
@settitle GoVPN
@copying
-This manual is for GoVPN -- simple secure free software virtual private
-network daemon, aimed to be reviewable, secure, DPI/censorship-resistant,
+This manual is for GoVPN -- simple free software virtual private network
+daemon, aimed to be reviewable, secure, DPI/censorship-resistant,
written on Go.
Copyright @copyright{} 2014-2015 @email{stargrave@@stargrave.org, Sergey Matveev}
@node Top
@top GoVPN
-GoVPN is simple secure free software virtual private network daemon,
-aimed to be reviewable, secure and
-@url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}/censorship-resistant.
-
-@itemize @bullet
-@item
-Copylefted free software: licensed under
-@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}.
-@item
-Fast strong @ref{PAKE, password authenticated} augmented key agreement
-(PAKE DH A-EKE) @ref{Handshake protocol, handshake}.
-@item
-Mutual two-side zero-knowledge peers authentication.
-@item
-@ref{Verifier structure, Augmented authentication tokens} resistant to
-offline dictionary attacks. An attacker can not masquerade a client
-even with server password verifiers compromising.
-@item
-Encrypted and authenticated @ref{Transport protocol, payload transport}
-with 128-bit @ref{Developer manual, security margin} state-of-the-art
-cryptography and censorship resistance (indistinguishability from noise).
-@item
-@url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy}
-property.
-@item
-Replay attack protection (using one-time MACs).
-@item
-Built-in rehandshake (session key rotation) and heartbeat features.
-@item
-Ability to hide payload packets length with the @ref{Noise, noise} data.
-@item
-Ability to hide payload timestamps with @ref{CPR, constant packet rate}
-traffic.
-@item
-Compatible with @url{http://egd.sourceforge.net/, EGD} (entropy
-gathering daemon) PRNGs.
-@item
-Several simultaneous clients support with per-client configuration
-options. Clients have pre-established @ref{Identity, identity} invisible
-for third-parties (they are anonymous).
-@item
-Uses @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP}
-underlying network interfaces.
-@item
-Can use @ref{Network transport, UDP and TCP} or HTTP @ref{Proxy, proxies}
-for accessing the server.
-@item
-Fully IPv4 and IPv6 compatible.
-@item
-Optional built-in HTTP-server for retrieving
-@ref{Stats, statistics} information about known connected peers in
-@url{http://json.org/, JSON} format.
-@item
-Written on on @url{http://golang.org/, Go} programming language with
-simple code that can be read and reviewed.
-@item
-@url{https://www.gnu.org/, GNU}/Linux and
-@url{http://www.freebsd.org/, FreeBSD} support.
-@end itemize
-
-@include media.texi
+@include about.texi
@menu
+* Подробнее о демоне: About RU.
* News::
* Installation::
* Precautions::
-* User manual::
-* Developer manual::
-* Contacts::
-* Copying conditions::
+* User manual: User.
+* Developer manual: Developer.
+* Contacts and feedback: Contacts.
* Thanks::
+* In the media: Media.
* TODO::
+* Copying conditions::
@end menu
+@include about.ru.texi
@include news.texi
@include installation.texi
@include precautions.texi
@include user.texi
@include developer.texi
@include contacts.texi
+@include thanks.texi
+@include media.texi
+@include todo.texi
@node Copying conditions
@unnumbered Copying conditions
@insertcopying
@verbatiminclude fdl.txt
-
-@include thanks.texi
-@include todo.texi
@bye
-@node Handshake protocol
+@node Handshake
@section Handshake protocol
@verbatiminclude handshake.utxt
Each handshake message ends with so called @code{IDtag}: it is an XTEA
encrypted first 64 bits of each message with client's @ref{Identity} as
a key. It is used to transmit identity and to mark packet as handshake
-message. Server can determine used identity by trying all possible known
-to him keys. It consumes resources, but XTEA is rather fast algorithm
-and handshake messages checking is seldom enough event.
+message.
+
+If @ref{Noise} is enabled, then junk data is inserted before
+@code{IDtag} to full up packet to MTU's size.
@strong{Preparation stage}:
@example
% ./utils/newclient.sh doesnotmatter
-Place verifier to peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier
+Your id is: 7012df29deee2170594119df5091d4a2
@end example
-"6d4ac605ce8dc37c2f0bf21cb542a713" is client's identity.
+@code{7012df29deee2170594119df5091d4a2} is client's identity.
@node Installation
@unnumbered Installation
+Possibly GoVPN already exists in your distribution:
+
+@itemize @bullet
+@item @url{https://aur.archlinux.org/packages/govpn/, AUR}
+@item @url{http://www.freshports.org/security/govpn/, FreeBSD ports}
+@end itemize
+
GoVPN is written on Go programming language and you have to install Go
compiler (1.5+ version is highly recommended): @code{lang/go} port in
FreeBSD and @code{golang} package in most GNU/Linux distributions.
@item @code{github.com/bigeagle/water} @tab GNU/Linux @tab BSD 3-Clause
@end multitable
-Get @ref{Prepared tarballs, the tarball}, check its
-@ref{Tarballs integrity check, authenticity} and run @code{make}.
+Get @ref{Tarballs, the tarball}, check its
+@ref{Integrity, integrity and authenticity} and run @code{make}.
@emph{govpn-client}, @emph{govpn-server}, @emph{govpn-verifier}
binaries will be built in the current directory:
install binaries, info-documentation and utilities.
@menu
-* Prepared tarballs::
-* Development source code::
-* Tarballs integrity check::
+* Prepared tarballs: Tarballs.
+* Tarballs integrity check: Integrity.
+* Development source code: Sources.
@end menu
@include download.texi
-@include sources.texi
@include integrity.texi
+@include sources.texi
-@node Tarballs integrity check
+@node Integrity
@section Tarballs integrity check
You @strong{have to} verify downloaded archives integrity and check
their signature to be sure that you have got trusted, untampered
software. For integrity and authentication of downloaded binaries
@url{https://www.gnupg.org/, The GNU Privacy Guard} is used. You must
-download signature provided with the tarball.
+download signature (.sig) provided with the tarball.
For the very first time you need to import signing public keys. They
are provided below, but be sure that you are reading them from the
-In the media:
+@node Media
+@unnumbered In the media
@itemize @bullet
@item @url{http://habrahabr.ru/company/ivi/blog/256365/, Реализуем безопасный VPN-протокол} (on russian)
-@node Network transport
+@node Network
@section Network transport
You can use either UDP or TCP underlying network transport protocols.
-TCP consumes more traffic: two additional bytes per packet. Also it is
-has more complex and slightly slower code. Moreover because of packet
-loss and TCP reliability it can lead to "meltdown" effect: significant
-performance decrease of underlying TCP connections. So generally TCP is
-not advisable for VPNs, but it can help with some nasty firewalls.
+TCP is more resource hungry. Moreover because of packet loss and TCP
+reliability it can lead to "meltdown" effect: significant performance
+loss of underlying TCP connections. Generally TCP is not advisable for
+VPNs, but it can help with some nasty firewalls.
@table @strong
+@item Release 4.0
+@itemize @bullet
+@item Handshake messages can be noised: their messages lengths are
+hidden. Now they are indistinguishable from transport messages.
+@item Parallelized clients processing on the server side.
+@item Much higher overall performance.
+@item Single JSON file server configuration.
+@end itemize
+
@item Release 3.5
@itemize @bullet
-@item Ability to use @ref{Network transport, TCP} network transport.
+@item Ability to use @ref{Network, TCP} network transport.
Server can listen on both UDP and TCP sockets.
@item Ability to use @ref{Proxy, HTTP proxies} (through CONNECT method)
for accessing the server. Server can also emulate HTTP proxy behaviour.
statistics.
@item
-Documentation is explicitly licensed under GNU FDL 1.3+.
+Documentation is explicitly licenced under GNU FDL 1.3+.
@end itemize
@item Release 2.3
user use-cases:
@itemize @bullet
-@item Compromising of password files on either server or client side
+@item Compromising of passphrase files on either server or client side
allows attacker to masquerade himself a client.
@item To prevent compromising of keys on the client side, one needs some
kind of passphrase protected secure storage (like either PGP with
Overall security on the client side is concentrated in passphrase
(high-entropy password), so it is convenient to use it in GoVPN
-directly, without static on-disk keys. That is why we use password
+directly, without static on-disk keys. That is why we use passphrase
authenticated key agreement.
We use "passphrase" term instead of "password". Technically there may be
-@node Server part
+@node Server
@section Server part
Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server
@table @code
@item -proto
-@ref{Network transport} to use. Can be @emph{udp}, @emph{tcp} or @emph{all}.
+@ref{Network, network protocol} to use. Can be @emph{udp} (default),
+@emph{tcp} or @emph{all}.
@item -bind
Address (@code{host:port} format) we must bind to.
-@item -peers
-Path to the directory containing peers information, database.
+@item -conf
+Path to JSON file with the configuration.
@item -proxy
Start trivial HTTP @ref{Proxy} server on specified @emph{host:port}.
@end table
-Peers directory must contain subdirectories with the names of client's
-identities in hexadecimal notation. Each subdirectory has the following
-files:
-
-@table @code
-
-@item verifier
-@strong{Required}. Contains corresponding verifier used to authenticate
-the client in hexadecimal notation. See @ref{Verifier} for how
-to create it.
-
-@item up.sh
-@strong{Required}. up-script executes each time connection with the
-client is established. It's @emph{stdout} output must contain TAP
-interface name on the first string. This script can be simple
-@code{echo tap10}, or maybe more advanced like this:
- @example
- #!/bin/sh
- $tap=$(ifconfig tap create)
- ifconfig $tap inet6 fc00::1/96 mtu 1412 up
- echo $tap
- @end example
-
-@item down.sh
-Optional. Same as @code{up.sh} above, but executes when connection is
-lost.
-
-@item name
-Optional. Contains human readable username. Used to beauty output of
-@ref{Stats}.
-
-@item timeout
-Optional. Contains @ref{Timeout} setting (decimal notation) in seconds.
-Otherwise default minute timeout will be used.
-
-@item noise
-Optional. Contains either "1" (enable @ref{Noise} adding), or "0".
-
-@item cpr
-Optional. Contains @ref{CPR} setting (decimal notation) in KiB/sec.
-
-@end table
+Configuration file is JSON file with following example structure:
+
+@verbatim
+{
+ "9b40701bdaf522f2b291cb039490312": { <-- Peer identifier
+ "name": "stargrave", <-- OPTIONAL human readable name
+ "up": "./stargrave-up.sh", <-- up-script
+ "down": "./stargrave-down.sh", <-- OPTIONAL down-script
+ "timeout": 60, <-- OPTIONAL overriden timeout
+ "noise": true, <-- OPTIONAL noise enabler
+ (default: false)
+ "cpr": 64, <-- OPTIONAL constant packet
+ rate in KiB/sec
+ "verifier": "2c15bbdffc73193bea56db412bce1143c68ccbdaa9e2eade53a684497646a685"
+ },
+ [...]
+}
+@end verbatim
+
+See @ref{Verifier} for its description.
+
+up-script executes each time connection with the client is established.
+Its @emph{stdout} output must contain TAP interface name as the first
+line. This script can be simple @code{echo tap10}, or maybe more
+advanced like this:
+@example
+#!/bin/sh
+$tap=$(ifconfig tap create)
+ifconfig $tap inet6 fc00::1/96 mtu 1412 up
+echo $tap
+@end example
-Each minute server refreshes peers directory contents and adds newly
-appeared identities, deletes an obsolete ones.
+Each minute server rereads and refreshes peers configuration and adds
+newly appeared identities, deletes an obsolete ones.
You can use convenient @code{utils/newclient.sh} script for new client
creation:
-@example
+@verbatim
% ./utils/newclient.sh Alice
-Place verifier to peers/9b40701bdaf522f2b291cb039490312/verifier
-@end example
-
-@code{9b40701bdaf522f2b291cb039490312} is client's identification.
-@code{peers/9b40701bdaf522f2b291cb039490312/name} contains @emph{Alice},
-@code{peers/9b40701bdaf522f2b291cb039490312/verifier} contains dummy
-verifier and @code{peers/9b40701bdaf522f2b291cb039490312/up.sh} contains
-currently dummy empty up-script.
+[...]
+Your id is: 7012df29deee2170594119df5091d4a2
+
+Place the following JSON configuration entry on the server's side:
+
+ "906e34b98750c4f686d6c5489508763c": {
+ "name": "Alice",
+ "up": "/path/to/up.sh",
+ "verifier": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ }
+[...]
+@end verbatim
-@node Development source code
+@node Sources
@section Development source code
Development source code contains the latest version of the code. It may
be buggy. It does not contain compiled documentation and dependent
libraries source code. Because of that, it is not recommended for
-porters: use @ref{Prepared tarballs} instead.
+porters: use @ref{Tarballs} instead.
-You can obtain it by cloning Git repository and fetching dependent
-libraries source code as git submodules:
+You can obtain it by cloning @url{http://git-scm.com/, Git}
+@url{http://git.cypherpunks.ru/cgit.cgi/govpn.git/log/, repository}
+and fetching dependent libraries source code as git submodules:
@example
% git clone git://git.cypherpunks.ru/govpn.git govpn
@unnumbered TODO
@itemize
+@item Use Argon2:
+@url{https://password-hashing.net/, Password Hashing Competition}
+winner, instead of PBKDF2.
@item Ability to tunnel only specified TCP connections, without full
featured VPN solution. Similar to ssh -R.
-@item Ability to noise handshake packets sizes.
-@item Increase performance.
@item Randomize ports usage.
@end itemize
-@node Transport protocol
+@node Transport
@section Transport protocol
@verbatim
-[PktLen] + ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) +
- AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE))
+TAG || ENCRYPTED || NONCE --> PACKET
+ ^ ^ ^
+ | | |
+ | | +-------------+
+ | | |
+ | +-------------+ |
+ | | |
+ +--< AUTH(AUTH_KEY, ENCRYPTED || NONCE)
+ ^ ^
+ | |
++------------------------+ |
+| |
+| +---------------+
+| |
++--< ENCRYPT(KEY, NONCE, PAYLOAD)
+ ^ ^
+ | |
+ | +--< SIZE || DATA [|| NOISE]
+ |
+ +--< PRP(PRP_KEY, SERIAL)
@end verbatim
-All transport and handshake messages are indistinguishable from
-pseudo random noise, except when using TCP connections.
-
-@code{PktLen} is used only with TCP connections. It is big-endian
-@emph{uint16} length of the whole packet (except @code{PktLen} itself).
-
@code{SERIAL} is message's serial number. Odds are reserved for
client(->server) messages, evens for server(->client) messages.
-@code{ENCn} is XTEA block cipher algorithm used here as PRP (pseudo
+@code{PRP} is XTEA block cipher algorithm used here as PRP (pseudo
random permutation function) to obfuscate @code{SERIAL}. Plaintext
@code{SERIAL} state is kept in peers internal state, but encrypted
-before transmission. XTEA is compact and fast enough. Salsa20 is PRF
-function and requires much more code to create PRP from it.
+before transmission.
XTEA's encryption key is the first 128-bit of Salsa20's output with
established common key and zero nonce (message nonces start from 1).
-@code{ENC} is Salsa20 stream cipher, with established session @code{KEY}
-and obfuscated @code{SERIAL} used as a nonce. First 256 bits of
-Salsa20's output is used as Poly1305 authentication key, next 256 bits
-are ignored. All remaining output is XORed with the data, encrypting it.
+@verbatim
+PRP_KEY = ENCRYPT(KEY, 0, 128-bit)
+@end verbatim
+
+@code{ENCRYPT} is Salsa20 stream cipher, with established session
+@code{KEY} and obfuscated @code{SERIAL} used as a nonce. 512 bit of
+Salsa20's output is ignored and only remaining is XORed with ther data,
+encrypting it.
-@code{DATA_SIZE} is big-endian @emph{uint16} storing length of the
+@code{SIZE} is big-endian @emph{uint16} storing length of the
@code{DATA}.
@code{NOISE} is optional. It is just some junk data, intended to fill up
packet to MTU size. This is useful for concealing payload packets length.
@code{AUTH} is Poly1305 authentication function. First 256 bits of
-Salsa20 output are used as a one-time key for @code{AUTH}.
+Salsa20's output are used as a one-time key for @code{AUTH}.
+
+@verbatim
+AUTH_KEY = ENCRYPT(KEY, NONCE, 256 bit)
+@end verbatim
To prevent replay attacks we must remember received @code{SERIAL}s and
-if meet one, then drop it. Basically we could just store latest number
-and check if received one is greater, but because of UDP packets
-reordering this can lead to valid packets dropping and overall
-performance degradation. We store up to 256 seen nonces in hash
-structure, in two swapping buckets.
+drop when receiving duplicate ones.
-@node User manual
+@node User
@unnumbered User manual
Announcements about updates and new releases can be found in @ref{Contacts}.
-GoVPN is split into two pieces: client and server. Each of them work on
-top of UDP/TCP and TAP virtual network interfaces. GoVPN is just a
-tunnelling of Ethernet frames, nothing less, nothing more. All you
-IP-related network management is not touched by VPN at all. You can
-automate it using up and down shell scripts.
+GoVPN is split into two pieces: @ref{Client} and @ref{Server}. Each of
+them work on top of @ref{Network, UDP/TCP} and TAP virtual network
+interfaces. GoVPN is just a tunnelling of Ethernet frames, nothing less,
+nothing more. All you IP-related network management is not touched by
+VPN at all. You can automate it using up and down shell scripts.
What network performance can user expect? For example single
@emph{Intel i5-2450M 2.5 GHz} core on @emph{FreeBSD 10.2 amd64}
-with @emph{Go 1.5} gives 435 Mbps TCP (over UDP) throughput.
+with @emph{Go 1.5.1} gives 786 Mbps (UDP transport) throughput.
@menu
* EGD:: Entropy gathering daemon
* Identity::
* PAKE:: Password Authenticated Key Agreement
* Timeout::
-* Network transport::
+* Network transport: Network.
* Proxy::
* MTU:: Maximum Transmission Unit
* Stats::
* Noise::
* CPR:: Constant Packet Rate
* Verifier::
-* Client part::
-* Server part::
-* Example usage::
+* Client part: Client.
+* Server part: Server.
+* Example usage: Example.
@end menu
@include egd.texi
210e3878542828901a3af9b4aa00b004de530410eef5c1ba2ffb6d04504371b2
@end example
-Store "210...1b2" string on the server's side in corresponding
-@code{verifier} file.
+Store @code{210...1b2} string on the server's side in corresponding
+@code{verifier} configuration file's field.
You can check passphrase against verifier by specifying @code{-verifier}
option with the path to verifier file:
-Subproject commit d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
+Subproject commit 278e1ec8e8a6e017cd07577924d6766039146ced
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Simple secure free software virtual private network daemon client.
+// Simple secure, DPI/censorship-resistant free software VPN daemon client.
package main
import (
"flag"
- "io"
"log"
"net"
"os"
noisy = flag.Bool("noise", false, "Enable noise appending")
cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
egdPath = flag.String("egd", "", "Optional path to EGD socket")
+
+ conf *govpn.PeerConf
+ tap *govpn.TAP
+ timeout int
+ firstUpCall bool = true
+ knownPeers govpn.KnownPeers
+ idsCache govpn.CipherCache
)
func main() {
flag.Parse()
- timeout := *timeoutP
+ timeout = *timeoutP
var err error
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
}
pub, priv := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath))
- conf := &govpn.PeerConf{
- Id: id,
- Timeout: time.Second * time.Duration(timeout),
- NoiseEnable: *noisy,
- CPR: *cpr,
- DSAPub: pub,
- DSAPriv: priv,
- }
- govpn.PeersInitDummy(id, conf)
-
- var conn io.Writer
- var sink chan []byte
- var ready chan struct{}
- switch *proto {
- case "udp":
- conn, sink, ready = startUDP()
- case "tcp":
- if *proxyAddr != "" {
- conn, sink, ready = proxyTCP()
- } else {
- conn, sink, ready = startTCP()
- }
- default:
- log.Fatalln("Unknown protocol specified")
+ conf = &govpn.PeerConf{
+ Id: id,
+ Timeout: time.Second * time.Duration(timeout),
+ Noise: *noisy,
+ CPR: *cpr,
+ DSAPub: pub,
+ DSAPriv: priv,
}
+ idsCache = govpn.NewCipherCache([]govpn.PeerId{*id})
+ log.Println(govpn.VersionGet())
- tap, ethSink, ethReady, _, err := govpn.TAPListen(
- *ifaceName,
- time.Second*time.Duration(timeout),
- *cpr,
- )
+ tap, err = govpn.TAPListen(*ifaceName)
if err != nil {
log.Fatalln("Can not listen on TAP interface:", err)
}
- timeouts := 0
- firstUpCall := true
- var peer *govpn.Peer
- var ethPkt []byte
- var pkt []byte
- knownPeers := govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
-
- log.Println(govpn.VersionGet())
- log.Println("Connected to", *proto, *remoteAddr)
log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU())
if *stats != "" {
log.Println("Stats are going to listen on", *stats)
termSignal := make(chan os.Signal, 1)
signal.Notify(termSignal, os.Interrupt, os.Kill)
- log.Println("Starting handshake")
- handshake := govpn.HandshakeStart(*remoteAddr, conn, conf)
-
MainCycle:
for {
- if peer != nil && (peer.BytesIn+peer.BytesOut) > govpn.MaxBytesPerKey {
- peer.Zero()
- peer = nil
- handshake = govpn.HandshakeStart(*remoteAddr, conn, conf)
- log.Println("Rehandshaking")
+ timeouted := make(chan struct{})
+ rehandshaking := make(chan struct{})
+ termination := make(chan struct{})
+ switch *proto {
+ case "udp":
+ go startUDP(timeouted, rehandshaking, termination)
+ case "tcp":
+ if *proxyAddr != "" {
+ go proxyTCP(timeouted, rehandshaking, termination)
+ } else {
+ go startTCP(timeouted, rehandshaking, termination)
+ }
+ default:
+ log.Fatalln("Unknown protocol specified")
}
select {
case <-termSignal:
+ log.Fatalln("Finishing")
+ termination <- struct{}{}
break MainCycle
- case ethPkt = <-ethSink:
- if peer == nil {
- if len(ethPkt) > 0 {
- ethReady <- struct{}{}
- }
- continue
- }
- peer.EthProcess(ethPkt, ethReady)
- case pkt = <-sink:
- timeouts++
- if timeouts >= timeout {
- break MainCycle
- }
- if pkt == nil {
- ready <- struct{}{}
- continue
- }
-
- if peer == nil {
- if govpn.IDsCache.Find(pkt) == nil {
- log.Println("Invalid identity in handshake packet")
- ready <- struct{}{}
- continue
- }
- if p := handshake.Client(pkt); p != nil {
- log.Println("Handshake completed")
- if firstUpCall {
- go govpn.ScriptCall(*upPath, *ifaceName)
- firstUpCall = false
- }
- peer = p
- handshake.Zero()
- handshake = nil
- }
- ready <- struct{}{}
- continue
- }
- if peer == nil {
- ready <- struct{}{}
- continue
- }
- if peer.PktProcess(pkt, tap, ready) {
- timeouts = 0
- }
+ case <-timeouted:
+ break MainCycle
+ case <-rehandshaking:
}
+ close(timeouted)
+ close(rehandshaking)
+ close(termination)
}
govpn.ScriptCall(*downPath, *ifaceName)
}
import (
"bufio"
"encoding/base64"
- "io"
"log"
"net"
"net/http"
)
-func proxyTCP() (io.Writer, chan []byte, chan struct{}) {
+func proxyTCP(timeouted, rehandshaking, termination chan struct{}) {
proxyAddr, err := net.ResolveTCPAddr("tcp", *proxyAddr)
if err != nil {
log.Fatalln("Can not resolve proxy address:", err)
if err != nil || resp.StatusCode != http.StatusOK {
log.Fatalln("Unexpected response from proxy")
}
- sink := make(chan []byte)
- ready := make(chan struct{})
- go handleTCP(conn, sink, ready)
- go func() { ready <- struct{}{} }()
- return TCPSender{conn}, sink, ready
+ log.Println("Connected to proxy:", *proxyAddr)
+ go handleTCP(conn, timeouted, rehandshaking, termination)
}
package main
import (
- "encoding/binary"
- "io"
+ "bytes"
"log"
"net"
+ "sync/atomic"
+ "time"
"govpn"
)
-// TCPSender prepends size prefix to each outgoing packet.
-type TCPSender struct {
- conn *net.TCPConn
-}
-
-func (c TCPSender) Write(data []byte) (int, error) {
- size := make([]byte, 2)
- binary.BigEndian.PutUint16(size, uint16(len(data)))
- return c.conn.Write(append(size, data...))
-}
-
-func startTCP() (io.Writer, chan []byte, chan struct{}) {
+func startTCP(timeouted, rehandshaking, termination chan struct{}) {
remote, err := net.ResolveTCPAddr("tcp", *remoteAddr)
if err != nil {
log.Fatalln("Can not resolve remote address:", err)
}
- c, err := net.DialTCP("tcp", nil, remote)
- conn := TCPSender{c}
+ conn, err := net.DialTCP("tcp", nil, remote)
if err != nil {
- log.Fatalln("Can not connect TCP:", err)
+ log.Fatalln("Can not connect to address:", err)
}
- sink := make(chan []byte)
- ready := make(chan struct{})
- go handleTCP(c, sink, ready)
- go func() { ready <- struct{}{} }()
- return conn, sink, ready
+ log.Println("Connected to TCP:" + *remoteAddr)
+ handleTCP(conn, timeouted, rehandshaking, termination)
}
-func handleTCP(conn *net.TCPConn, sink chan []byte, ready chan struct{}) {
- var err error
- var n int
- var sizeNbuf int
- sizeBuf := make([]byte, 2)
- var sizeNeed uint16
- var bufN uint16
+func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan struct{}) {
+ hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
buf := make([]byte, govpn.MTU)
+ var n int
+ var err error
+ var prev int
+ var peer *govpn.Peer
+ var terminator chan struct{}
+HandshakeCycle:
for {
- <-ready
- if sizeNbuf != 2 {
- n, err = conn.Read(sizeBuf[sizeNbuf:2])
- if err != nil {
- break
- }
- sizeNbuf += n
- if sizeNbuf != 2 {
- sink <- nil
- continue
- }
- sizeNeed = binary.BigEndian.Uint16(sizeBuf)
- if int(sizeNeed) > govpn.MTU-2 {
- log.Println("Invalid TCP size, skipping")
- sizeNbuf = 0
- sink <- nil
- continue
- }
- bufN = 0
+ select {
+ case <-termination:
+ break HandshakeCycle
+ default:
+ }
+ if prev == govpn.MTU {
+ log.Println("Timeouted waiting for the packet")
+ timeouted <- struct{}{}
+ break HandshakeCycle
+ }
+
+ conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ log.Println("Connection timeouted")
+ timeouted <- struct{}{}
+ break HandshakeCycle
+ }
+
+ prev += n
+ peerId := idsCache.Find(buf[:prev])
+ if peerId == nil {
+ continue
+ }
+ peer = hs.Client(buf[:prev])
+ prev = 0
+ if peer == nil {
+ continue
}
- ReadMore:
- if sizeNeed != bufN {
- n, err = conn.Read(buf[bufN:sizeNeed])
- if err != nil {
- break
+ log.Println("Handshake completed")
+ knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
+ if firstUpCall {
+ go govpn.ScriptCall(*upPath, *ifaceName)
+ firstUpCall = false
+ }
+ hs.Zero()
+ terminator = make(chan struct{})
+ go func() {
+ heartbeat := time.NewTicker(peer.Timeout)
+ var data []byte
+ Processor:
+ for {
+ select {
+ case <-heartbeat.C:
+ peer.EthProcess(nil)
+ case <-terminator:
+ break Processor
+ case data = <-tap.Sink:
+ peer.EthProcess(data)
+ }
}
- bufN += uint16(n)
- goto ReadMore
+ heartbeat.Stop()
+ peer.Zero()
+ }()
+ break HandshakeCycle
+ }
+ if hs != nil {
+ hs.Zero()
+ }
+ if peer == nil {
+ return
+ }
+
+ nonceExpectation := make([]byte, govpn.NonceSize)
+ peer.NonceExpectation(nonceExpectation)
+ prev = 0
+ var i int
+TransportCycle:
+ for {
+ select {
+ case <-termination:
+ break TransportCycle
+ default:
}
- sizeNbuf = 0
- sink <- buf[:sizeNeed]
+ if prev == govpn.MTU {
+ log.Println("Timeouted waiting for the packet")
+ timeouted <- struct{}{}
+ break TransportCycle
+ }
+ conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ log.Println("Connection timeouted")
+ timeouted <- struct{}{}
+ break TransportCycle
+ }
+ prev += n
+ CheckMore:
+ if prev < govpn.MinPktLength {
+ continue
+ }
+ i = bytes.Index(buf[:prev], nonceExpectation)
+ if i == -1 {
+ continue
+ }
+ if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
+ log.Println("Unauthenticated packet, dropping connection")
+ timeouted <- struct{}{}
+ break TransportCycle
+ }
+ if atomic.LoadInt64(&peer.BytesIn)+atomic.LoadInt64(&peer.BytesOut) > govpn.MaxBytesPerKey {
+ log.Println("Need rehandshake")
+ rehandshaking <- struct{}{}
+ break TransportCycle
+ }
+ peer.NonceExpectation(nonceExpectation)
+ copy(buf, buf[i+govpn.NonceSize:prev])
+ prev = prev - i - govpn.NonceSize
+ goto CheckMore
+ }
+ if terminator != nil {
+ terminator <- struct{}{}
}
+ peer.Zero()
+ conn.Close()
}
package main
import (
- "io"
"log"
"net"
+ "sync/atomic"
"time"
"govpn"
)
-func startUDP() (io.Writer, chan []byte, chan struct{}) {
+func startUDP(timeouted, rehandshaking, termination chan struct{}) {
remote, err := net.ResolveUDPAddr("udp", *remoteAddr)
if err != nil {
log.Fatalln("Can not resolve remote address:", err)
}
- c, err := net.DialUDP("udp", nil, remote)
- conn := io.Writer(c)
+ conn, err := net.DialUDP("udp", nil, remote)
if err != nil {
log.Fatalln("Can not listen on UDP:", err)
}
- sink := make(chan []byte)
- ready := make(chan struct{})
- go func() {
- buf := make([]byte, govpn.MTU)
- var n int
- var err error
- for {
- <-ready
- c.SetReadDeadline(time.Now().Add(time.Second))
- n, err = c.Read(buf)
- if err != nil {
- sink <- nil
- continue
+ log.Println("Connected to UDP:" + *remoteAddr)
+
+ hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
+ buf := make([]byte, govpn.MTU)
+ var n int
+ var timeouts int
+ var peer *govpn.Peer
+ var terminator chan struct{}
+MainCycle:
+ for {
+ select {
+ case <-termination:
+ break MainCycle
+ default:
+ }
+
+ conn.SetReadDeadline(time.Now().Add(time.Second))
+ n, err = conn.Read(buf)
+ if timeouts == timeout {
+ log.Println("Timeouted")
+ timeouted <- struct{}{}
+ break
+ }
+ if err != nil {
+ timeouts++
+ continue
+ }
+ if peer != nil {
+ if peer.PktProcess(buf[:n], tap, true) {
+ timeouts = 0
+ } else {
+ log.Println("Unauthenticated packet")
+ timeouts++
+ }
+ if atomic.LoadInt64(&peer.BytesIn)+atomic.LoadInt64(&peer.BytesOut) > govpn.MaxBytesPerKey {
+ log.Println("Need rehandshake")
+ rehandshaking <- struct{}{}
+ break MainCycle
}
- sink <- buf[:n]
+ continue
+ }
+ if idsCache.Find(buf[:n]) == nil {
+ log.Println("Invalid identity in handshake packet")
+ continue
}
- }()
- ready <- struct{}{}
- return conn, sink, ready
+ timeouts = 0
+ peer = hs.Client(buf[:n])
+ if peer == nil {
+ continue
+ }
+ log.Println("Handshake completed")
+ knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
+ if firstUpCall {
+ go govpn.ScriptCall(*upPath, *ifaceName)
+ firstUpCall = false
+ }
+ hs.Zero()
+ terminator = make(chan struct{})
+ go func() {
+ heartbeat := time.NewTicker(peer.Timeout)
+ var data []byte
+ Processor:
+ for {
+ select {
+ case <-heartbeat.C:
+ peer.EthProcess(nil)
+ case <-terminator:
+ break Processor
+ case data = <-tap.Sink:
+ peer.EthProcess(data)
+ }
+ }
+ heartbeat.Stop()
+ peer.Zero()
+ }()
+ }
+ if terminator != nil {
+ terminator <- struct{}{}
+ }
+ if hs != nil {
+ hs.Zero()
+ }
+ conn.Close()
}
--- /dev/null
+package main
+
+import (
+ "bytes"
+ "sync"
+ "time"
+
+ "govpn"
+)
+
+type PeerState struct {
+ peer *govpn.Peer
+ terminator chan struct{}
+ tap *govpn.TAP
+}
+
+var (
+ handshakes map[string]*govpn.Handshake = make(map[string]*govpn.Handshake)
+ hsLock sync.RWMutex
+
+ peers map[string]*PeerState = make(map[string]*PeerState)
+ peersLock sync.RWMutex
+
+ peersById map[govpn.PeerId]string = make(map[govpn.PeerId]string)
+ peersByIdLock sync.RWMutex
+
+ knownPeers govpn.KnownPeers
+ kpLock sync.RWMutex
+)
+
+func peerReady(ps PeerState) {
+ var data []byte
+ heartbeat := time.NewTicker(ps.peer.Timeout)
+Processor:
+ for {
+ select {
+ case <-heartbeat.C:
+ ps.peer.EthProcess(nil)
+ case <-ps.terminator:
+ break Processor
+ case data = <-ps.tap.Sink:
+ ps.peer.EthProcess(data)
+ }
+ }
+ close(ps.terminator)
+ ps.peer.Zero()
+ heartbeat.Stop()
+}
+
+func callUp(peerId *govpn.PeerId) (string, error) {
+ result, err := govpn.ScriptCall(confs[*peerId].Up, "")
+ if err != nil {
+ return "", err
+ }
+ sepIndex := bytes.Index(result, []byte{'\n'})
+ if sepIndex < 0 {
+ sepIndex = len(result)
+ }
+ ifaceName := string(result[:sepIndex])
+ return ifaceName, nil
+}
--- /dev/null
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "time"
+
+ "github.com/agl/ed25519"
+
+ "govpn"
+)
+
+const (
+ RefreshRate = time.Minute
+)
+
+var (
+ confs map[govpn.PeerId]*govpn.PeerConf
+ idsCache govpn.CipherCache
+)
+
+func confRead() map[govpn.PeerId]*govpn.PeerConf {
+ data, err := ioutil.ReadFile(*confPath)
+ if err != nil {
+ log.Fatalln("Unable to read configuration:", err)
+ }
+ confsRaw := new(map[string]govpn.PeerConf)
+ err = json.Unmarshal(data, confsRaw)
+ if err != nil {
+ log.Fatalln("Unable to parse configuration:", err)
+ }
+
+ confs := make(map[govpn.PeerId]*govpn.PeerConf, len(*confsRaw))
+ for peerIdRaw, pc := range *confsRaw {
+ peerId, err := govpn.IDDecode(peerIdRaw)
+ if err != nil {
+ log.Fatalln("Invalid peer ID:", peerIdRaw, err)
+ }
+ conf := govpn.PeerConf{
+ Id: peerId,
+ Name: pc.Name,
+ Up: pc.Up,
+ Down: pc.Down,
+ Noise: pc.Noise,
+ CPR: pc.CPR,
+ }
+ if pc.TimeoutInt <= 0 {
+ pc.TimeoutInt = govpn.TimeoutDefault
+ }
+ conf.Timeout = time.Second * time.Duration(pc.TimeoutInt)
+
+ if len(pc.Verifier) != ed25519.PublicKeySize*2 {
+ log.Fatalln("Verifier must be 64 hex characters long")
+ }
+ keyDecoded, err := hex.DecodeString(string(pc.Verifier))
+ if err != nil {
+ log.Fatalln("Unable to decode the key:", err.Error(), pc.Verifier)
+ }
+ conf.DSAPub = new([ed25519.PublicKeySize]byte)
+ copy(conf.DSAPub[:], keyDecoded)
+
+ confs[*peerId] = &conf
+ }
+ return confs
+}
+
+func confRefresh() {
+ confs = confRead()
+ ids := make([]govpn.PeerId, 0, len(confs))
+ for peerId, _ := range confs {
+ ids = append(ids, peerId)
+ }
+ idsCache.Update(ids)
+}
+
+func confInit() {
+ idsCache = govpn.NewCipherCache(nil)
+ confRefresh()
+ go func() {
+ for {
+ time.Sleep(RefreshRate)
+ confRefresh()
+ }
+ }()
+}
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Simple secure free software virtual private network daemon.
+// Simple secure, DPI/censorship-resistant free software VPN daemon.
package main
import (
- "bytes"
"flag"
- "io"
"log"
"net"
"os"
"os/signal"
- "path"
"time"
"govpn"
)
var (
- bindAddr = flag.String("bind", "[::]:1194", "Bind to address")
- proto = flag.String("proto", "udp", "Protocol to use: udp, tcp or all")
- peersPath = flag.String("peers", "peers", "Path to peers keys directory")
- stats = flag.String("stats", "", "Enable stats retrieving on host:port")
- proxy = flag.String("proxy", "", "Enable HTTP proxy on host:port")
- mtu = flag.Int("mtu", 1452, "MTU for outgoing packets")
- egdPath = flag.String("egd", "", "Optional path to EGD socket")
+ bindAddr = flag.String("bind", "[::]:1194", "Bind to address")
+ proto = flag.String("proto", "udp", "Protocol to use: udp, tcp or all")
+ confPath = flag.String("conf", "peers.json", "Path to configuration JSON")
+ stats = flag.String("stats", "", "Enable stats retrieving on host:port")
+ proxy = flag.String("proxy", "", "Enable HTTP proxy on host:port")
+ mtu = flag.Int("mtu", 1452, "MTU for outgoing packets")
+ egdPath = flag.String("egd", "", "Optional path to EGD socket")
)
-type Pkt struct {
- addr string
- conn io.Writer
- data []byte
- ready chan struct{}
-}
-
-type PeerReadyEvent struct {
- peer *govpn.Peer
- iface string
-}
-
-type PeerState struct {
- peer *govpn.Peer
- tap *govpn.TAP
- sink chan []byte
- ready chan struct{}
- terminate chan struct{}
-}
-
-func NewPeerState(peer *govpn.Peer, iface string) *PeerState {
- tap, sink, ready, terminate, err := govpn.TAPListen(iface, peer.Timeout, peer.CPR)
- if err != nil {
- log.Println("Unable to create Eth", err)
- return nil
- }
- state := PeerState{
- peer: peer,
- tap: tap,
- sink: sink,
- ready: ready,
- terminate: terminate,
- }
- return &state
-}
-
-type EthEvent struct {
- peer *govpn.Peer
- data []byte
- ready chan struct{}
-}
-
func main() {
flag.Parse()
timeout := time.Second * time.Duration(govpn.TimeoutDefault)
log.Println(govpn.VersionGet())
govpn.MTU = *mtu
- govpn.PeersInit(*peersPath)
+ confInit()
+ knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer))
if *egdPath != "" {
log.Println("Using", *egdPath, "EGD")
govpn.EGDInit(*egdPath)
}
- sink := make(chan Pkt)
switch *proto {
case "udp":
- startUDP(sink)
+ startUDP()
case "tcp":
- startTCP(sink)
+ startTCP()
case "all":
- startUDP(sink)
- startTCP(sink)
+ startUDP()
+ startTCP()
default:
log.Fatalln("Unknown protocol specified")
}
hsHeartbeat := time.Tick(timeout)
go func() { <-hsHeartbeat }()
- var state *govpn.Handshake
- var peerState *PeerState
- var peer *govpn.Peer
- var exists bool
- states := make(map[string]*govpn.Handshake)
- peers := make(map[string]*PeerState)
- peerReadySink := make(chan PeerReadyEvent)
- knownPeers := govpn.KnownPeers(make(map[string]**govpn.Peer))
- var peerReady PeerReadyEvent
- var pkt Pkt
- var ethEvent EthEvent
- var peerId *govpn.PeerId
- var peerConf *govpn.PeerConf
- var handshakeProcessForce bool
- ethSink := make(chan EthEvent)
-
log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU())
if *stats != "" {
log.Println("Stats are going to listen on", *stats)
go govpn.StatsProcessor(statsPort, &knownPeers)
}
if *proxy != "" {
- go proxyStart(sink)
+ go proxyStart()
}
log.Println("Server started")
+ var needsDeletion bool
MainCycle:
for {
select {
break MainCycle
case <-hsHeartbeat:
now := time.Now()
- for addr, hs := range states {
+ hsLock.Lock()
+ for addr, hs := range handshakes {
if hs.LastPing.Add(timeout).Before(now) {
log.Println("Deleting handshake state", addr)
hs.Zero()
- delete(states, addr)
+ delete(handshakes, addr)
}
}
- for addr, state := range peers {
- if state.peer.LastPing.Add(timeout).Before(now) {
- log.Println("Deleting peer", state.peer)
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ for addr, ps := range peers {
+ ps.peer.BusyR.Lock()
+ needsDeletion = ps.peer.LastPing.Add(timeout).Before(now)
+ ps.peer.BusyR.Unlock()
+ if needsDeletion {
+ log.Println("Deleting peer", ps.peer)
delete(peers, addr)
delete(knownPeers, addr)
- downPath := path.Join(
- govpn.PeersPath,
- state.peer.Id.String(),
- "down.sh",
+ delete(peersById, *ps.peer.Id)
+ go govpn.ScriptCall(
+ confs[*ps.peer.Id].Down,
+ ps.tap.Name,
)
- go govpn.ScriptCall(downPath, state.tap.Name)
- state.terminate <- struct{}{}
- state.peer.Zero()
+ ps.terminator <- struct{}{}
}
}
- case peerReady = <-peerReadySink:
- for addr, state := range peers {
- if state.tap.Name != peerReady.iface {
- continue
- }
- delete(peers, addr)
- delete(knownPeers, addr)
- state.terminate <- struct{}{}
- state.peer.Zero()
- break
- }
- state := NewPeerState(peerReady.peer, peerReady.iface)
- if state == nil {
- continue
- }
- peers[peerReady.peer.Addr] = state
- knownPeers[peerReady.peer.Addr] = &peerReady.peer
- states[peerReady.peer.Addr].Zero()
- delete(states, peerReady.peer.Addr)
- log.Println("Registered interface", peerReady.iface, "with peer", peer)
- go func(state *PeerState) {
- for data := range state.sink {
- ethSink <- EthEvent{
- peer: state.peer,
- data: data,
- ready: state.ready,
- }
- }
- }(state)
- case ethEvent = <-ethSink:
- if s, exists := peers[ethEvent.peer.Addr]; !exists || s.peer != ethEvent.peer {
- continue
- }
- ethEvent.peer.EthProcess(ethEvent.data, ethEvent.ready)
- case pkt = <-sink:
- if pkt.data == nil {
- pkt.ready <- struct{}{}
- continue
- }
- handshakeProcessForce = false
- HandshakeProcess:
- if _, exists = peers[pkt.addr]; handshakeProcessForce || !exists {
- peerId = govpn.IDsCache.Find(pkt.data)
- if peerId == nil {
- log.Println("Unknown identity from", pkt.addr)
- pkt.ready <- struct{}{}
- continue
- }
- peerConf = peerId.Conf()
- if peerConf == nil {
- log.Println("Can not get peer configuration", peerId.String())
- pkt.ready <- struct{}{}
- continue
- }
- state, exists = states[pkt.addr]
- if !exists {
- state = govpn.HandshakeNew(pkt.addr, pkt.conn, peerConf)
- states[pkt.addr] = state
- }
- peer = state.Server(pkt.data)
- if peer != nil {
- log.Println("Peer handshake finished", peer)
- if _, exists = peers[pkt.addr]; exists {
- go func() {
- peerReadySink <- PeerReadyEvent{
- peer, peers[pkt.addr].tap.Name,
- }
- }()
- } else {
- go func() {
- upPath := path.Join(govpn.PeersPath, peer.Id.String(), "up.sh")
- result, err := govpn.ScriptCall(upPath, "")
- if err != nil {
- return
- }
- sepIndex := bytes.Index(result, []byte{'\n'})
- if sepIndex < 0 {
- sepIndex = len(result)
- }
- ifaceName := string(result[:sepIndex])
- peerReadySink <- PeerReadyEvent{peer, ifaceName}
- }()
- }
- }
- if !handshakeProcessForce {
- pkt.ready <- struct{}{}
- }
- continue
- }
- peerState, exists = peers[pkt.addr]
- if !exists {
- pkt.ready <- struct{}{}
- continue
- }
- // If it fails during processing, then try to work with it
- // as with handshake packet
- if !peerState.peer.PktProcess(pkt.data, peerState.tap, pkt.ready) {
- handshakeProcessForce = true
- goto HandshakeProcess
- }
+ hsLock.Unlock()
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
}
}
}
"net/http"
)
-type proxyHandler struct {
- sink chan Pkt
-}
+type proxyHandler struct{}
func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
conn, _, err := w.(http.Hijacker).Hijack()
return
}
conn.Write([]byte("HTTP/1.0 200 OK\n\n"))
- ready := make(chan struct{}, 1)
- go handleTCP(conn, p.sink, ready)
- ready <- struct{}{}
-
+ go handleTCP(conn)
}
-func proxyStart(sink chan Pkt) {
- log.Println("HTTP proxy listening on:", *proxy)
+func proxyStart() {
+ log.Println("HTTP proxy listening on:" + *proxy)
s := &http.Server{
Addr: *proxy,
- Handler: proxyHandler{sink},
+ Handler: proxyHandler{},
}
log.Println("HTTP proxy result:", s.ListenAndServe())
}
package main
import (
- "encoding/binary"
+ "bytes"
"log"
"net"
+ "time"
"govpn"
)
-type TCPSender struct {
- conn net.Conn
-}
-
-func (c TCPSender) Write(data []byte) (int, error) {
- size := make([]byte, 2)
- binary.BigEndian.PutUint16(size, uint16(len(data)))
- return c.conn.Write(append(size, data...))
-}
-
-func startTCP(sink chan Pkt) {
+func startTCP() {
bind, err := net.ResolveTCPAddr("tcp", *bindAddr)
if err != nil {
log.Fatalln("Can not resolve bind address:", err)
if err != nil {
log.Fatalln("Can not listen on TCP:", err)
}
- log.Println("Listening on TCP", *bindAddr)
+ log.Println("Listening on TCP:" + *bindAddr)
go func() {
for {
- conn, _ := listener.AcceptTCP()
- ready := make(chan struct{}, 1)
- go handleTCP(conn, sink, ready)
- ready <- struct{}{}
+ conn, err := listener.AcceptTCP()
+ if err != nil {
+ log.Println("Error accepting TCP:", err)
+ continue
+ }
+ go handleTCP(conn)
}
}()
}
-func handleTCP(conn net.Conn, sink chan Pkt, ready chan struct{}) {
+func handleTCP(conn net.Conn) {
addr := conn.RemoteAddr().String()
- var err error
- var n int
- var sizeNbuf int
- sizeBuf := make([]byte, 2)
- var sizeNeed uint16
- var bufN uint16
buf := make([]byte, govpn.MTU)
+ var n int
+ var err error
+ var prev int
+ var hs *govpn.Handshake
+ var ps *PeerState
+ var peer *govpn.Peer
+ var tap *govpn.TAP
+ var conf *govpn.PeerConf
for {
- <-ready
- if sizeNbuf != 2 {
- n, err = conn.Read(sizeBuf[sizeNbuf:2])
- if err != nil {
+ if prev == govpn.MTU {
+ break
+ }
+ conn.SetReadDeadline(time.Now().Add(time.Duration(govpn.TimeoutDefault) * time.Second))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ // Either EOFed or timeouted
+ break
+ }
+ prev += n
+ peerId := idsCache.Find(buf[:prev])
+ if peerId == nil {
+ continue
+ }
+ if hs == nil {
+ conf = confs[*peerId]
+ if conf == nil {
+ log.Println("Can not get peer configuration:", peerId.String())
break
}
- sizeNbuf += n
- if sizeNbuf != 2 {
- sink <- Pkt{ready: ready}
- continue
+ hs = govpn.NewHandshake(addr, conn, conf)
+ }
+ peer = hs.Server(buf[:prev])
+ prev = 0
+ if peer == nil {
+ continue
+ }
+ hs.Zero()
+ log.Println("Peer handshake finished:", addr, peer.Id.String())
+ peersByIdLock.RLock()
+ addrPrev, exists := peersById[*peer.Id]
+ peersByIdLock.RUnlock()
+ if exists {
+ peersLock.Lock()
+ peers[addrPrev].terminator <- struct{}{}
+ tap = peers[addrPrev].tap
+ ps = &PeerState{
+ peer: peer,
+ tap: tap,
+ terminator: make(chan struct{}),
}
- sizeNeed = binary.BigEndian.Uint16(sizeBuf)
- if int(sizeNeed) > govpn.MTU-2 {
- log.Println("Invalid TCP size, skipping")
- sizeNbuf = 0
- sink <- Pkt{ready: ready}
- continue
+ go peerReady(*ps)
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ delete(peers, addrPrev)
+ delete(knownPeers, addrPrev)
+ peers[addr] = ps
+ knownPeers[addr] = &peer
+ peersById[*peer.Id] = addr
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ log.Println("Rehandshake processed:", peer.Id.String())
+ } else {
+ ifaceName, err := callUp(peer.Id)
+ if err != nil {
+ break
}
- bufN = 0
- }
- ReadMore:
- if sizeNeed != bufN {
- n, err = conn.Read(buf[bufN:sizeNeed])
+ tap, err = govpn.TAPListen(ifaceName)
if err != nil {
+ log.Println("Unable to create TAP:", err)
break
}
- bufN += uint16(n)
- goto ReadMore
+ ps = &PeerState{
+ peer: peer,
+ tap: tap,
+ terminator: make(chan struct{}, 1),
+ }
+ go peerReady(*ps)
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ peers[addr] = ps
+ peersById[*peer.Id] = addr
+ knownPeers[addr] = &peer
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ log.Println("Peer created:", peer.Id.String())
+ }
+ break
+ }
+ if hs != nil {
+ hs.Zero()
+ }
+ if peer == nil {
+ return
+ }
+
+ nonceExpectation := make([]byte, govpn.NonceSize)
+ peer.NonceExpectation(nonceExpectation)
+ prev = 0
+ var i int
+ for {
+ if prev == govpn.MTU {
+ break
+ }
+ conn.SetReadDeadline(time.Now().Add(conf.Timeout))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ // Either EOFed or timeouted
+ break
+ }
+ prev += n
+ CheckMore:
+ if prev < govpn.MinPktLength {
+ continue
+ }
+ i = bytes.Index(buf[:prev], nonceExpectation)
+ if i == -1 {
+ continue
}
- sizeNbuf = 0
- sink <- Pkt{
- addr,
- TCPSender{conn},
- buf[:sizeNeed],
- ready,
+ if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
+ log.Println(
+ "Unauthenticated packet, dropping connection",
+ addr, peer.Id.String(),
+ )
+ break
}
+ peer.NonceExpectation(nonceExpectation)
+ copy(buf, buf[i+govpn.NonceSize:prev])
+ prev = prev - i - govpn.NonceSize
+ goto CheckMore
}
+ peer.Zero()
}
import (
"log"
"net"
- "time"
"govpn"
)
return c.conn.WriteToUDP(data, c.addr)
}
-func startUDP(sink chan Pkt) {
+var (
+ // Buffers for UDP parallel processing
+ udpBufs chan []byte = make(chan []byte, 1<<8)
+)
+
+func startUDP() {
bind, err := net.ResolveUDPAddr("udp", *bindAddr)
- ready := make(chan struct{})
if err != nil {
log.Fatalln("Can not resolve bind address:", err)
}
- lconn, err := net.ListenUDP("udp", bind)
+ conn, err := net.ListenUDP("udp", bind)
if err != nil {
log.Fatalln("Can not listen on UDP:", err)
}
- log.Println("Listening on UDP", *bindAddr)
+ log.Println("Listening on UDP:" + *bindAddr)
+
+ udpBufs <- make([]byte, govpn.MTU)
go func() {
- buf := make([]byte, govpn.MTU)
- var n int
+ var buf []byte
var raddr *net.UDPAddr
+ var addr string
+ var n int
var err error
+ var ps *PeerState
+ var hs *govpn.Handshake
+ var addrPrev string
+ var exists bool
+ var peerId *govpn.PeerId
+ var peer *govpn.Peer
+ var conf *govpn.PeerConf
for {
- <-ready
- lconn.SetReadDeadline(time.Now().Add(time.Second))
- n, raddr, err = lconn.ReadFromUDP(buf)
+ buf = <-udpBufs
+ n, raddr, err = conn.ReadFromUDP(buf)
if err != nil {
- sink <- Pkt{ready: ready}
- continue
+ log.Println("Unexpected error when receiving", err)
+ break
+ }
+ addr = raddr.String()
+
+ peersLock.RLock()
+ ps, exists = peers[addr]
+ peersLock.RUnlock()
+ if !exists {
+ goto CheckHandshake
+ }
+ go func(ps *govpn.Peer, tap *govpn.TAP, buf []byte, n int) {
+ peer.PktProcess(buf[:n], tap, true)
+ udpBufs <- buf
+ }(ps.peer, ps.tap, buf, n)
+ continue
+ CheckHandshake:
+ hsLock.RLock()
+ hs, exists = handshakes[addr]
+ hsLock.RUnlock()
+ if !exists {
+ goto CheckID
+ }
+ peer = hs.Server(buf[:n])
+ if peer == nil {
+ goto Finished
+ }
+
+ log.Println("Peer handshake finished:", addr, peer.Id.String())
+ hs.Zero()
+ hsLock.Lock()
+ delete(handshakes, addr)
+ hsLock.Unlock()
+
+ go func() {
+ udpBufs <- make([]byte, govpn.MTU)
+ udpBufs <- make([]byte, govpn.MTU)
+ }()
+ peersByIdLock.RLock()
+ addrPrev, exists = peersById[*peer.Id]
+ peersByIdLock.RUnlock()
+ if exists {
+ peersLock.Lock()
+ peers[addrPrev].terminator <- struct{}{}
+ ps = &PeerState{
+ peer: peer,
+ tap: peers[addrPrev].tap,
+ terminator: make(chan struct{}),
+ }
+ go func(ps PeerState) {
+ peerReady(ps)
+ <-udpBufs
+ <-udpBufs
+ }(*ps)
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ delete(peers, addrPrev)
+ delete(knownPeers, addrPrev)
+ peers[addr] = ps
+ knownPeers[addr] = &peer
+ peersById[*peer.Id] = addr
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ log.Println("Rehandshake processed:", peer.Id.String())
+ } else {
+ go func(addr string, peer *govpn.Peer) {
+ ifaceName, err := callUp(peer.Id)
+ if err != nil {
+ return
+ }
+ tap, err := govpn.TAPListen(ifaceName)
+ if err != nil {
+ log.Println("Unable to create TAP:", err)
+ return
+ }
+ ps = &PeerState{
+ peer: peer,
+ tap: tap,
+ terminator: make(chan struct{}),
+ }
+ go func(ps PeerState) {
+ peerReady(ps)
+ <-udpBufs
+ <-udpBufs
+ }(*ps)
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ peers[addr] = ps
+ knownPeers[addr] = &peer
+ peersById[*peer.Id] = addr
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ log.Println("Peer created:", peer.Id.String())
+ }(addr, peer)
+ }
+ goto Finished
+ CheckID:
+ peerId = idsCache.Find(buf[:n])
+ if peerId == nil {
+ log.Println("Unknown identity from:", addr)
+ goto Finished
}
- sink <- Pkt{
- raddr.String(),
- UDPSender{lconn, raddr},
- buf[:n],
- ready,
+ conf = confs[*peerId]
+ if conf == nil {
+ log.Println("Unable to get peer configuration:", peerId.String())
+ goto Finished
}
+ hs = govpn.NewHandshake(
+ addr,
+ UDPSender{conn: conn, addr: raddr},
+ conf,
+ )
+ hs.Server(buf[:n])
+ hsLock.Lock()
+ handshakes[addr] = hs
+ hsLock.Unlock()
+ Finished:
+ udpBufs <- buf
}
}()
- ready <- struct{}{}
}
"os"
"os/exec"
"runtime"
+ "time"
)
const (
return out, err
}
-// Zero each byte
+// Zero each byte.
func sliceZero(data []byte) {
for i := 0; i < len(data); i++ {
- data[i] = '\x00'
+ data[i] = 0
}
}
func VersionGet() string {
return "GoVPN version " + Version + " built with " + runtime.Version()
}
+
+func cprCycleCalculate(rate int) time.Duration {
+ if rate == 0 {
+ return time.Duration(0)
+ }
+ return time.Second / time.Duration(rate*(1<<10)/MTU)
+}
--- /dev/null
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+package govpn
+
+import (
+ "time"
+
+ "github.com/agl/ed25519"
+)
+
+type PeerConf struct {
+ Id *PeerId `json:"-"`
+ Name string `json:"name"`
+ Up string `json:"up"`
+ Down string `json:"down"`
+ TimeoutInt int `json:"timeout"`
+ Timeout time.Duration `json:"-"`
+ Noise bool `json:"noise"`
+ CPR int `json:"cpr"`
+ Verifier string `json:"verifier"`
+
+ // This is passphrase verifier
+ DSAPub *[ed25519.PublicKeySize]byte `json:"-"`
+ // This field exists only on client's side
+ DSAPriv *[ed25519.PrivateKeySize]byte `json:"-"`
+}
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Simple secure free software virtual private network daemon
+// Simple secure, DPI/censorship-resistant free software VPN daemon.
package govpn
}
// Create new handshake state.
-func HandshakeNew(addr string, conn io.Writer, conf *PeerConf) *Handshake {
+func NewHandshake(addr string, conn io.Writer, conf *PeerConf) *Handshake {
state := Handshake{
addr: addr,
conn: conn,
// for starting the handshake procedure. // First handshake packet
// will be sent immediately.
func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake {
- state := HandshakeNew(addr, conn, conf)
-
+ state := NewHandshake(addr, conn, conf)
var dhPubRepr *[32]byte
state.dhPriv, dhPubRepr = dhKeypairGen()
if err := randRead(state.rNonce[:]); err != nil {
log.Fatalln("Error reading random for nonce:", err)
}
- enc := make([]byte, 32)
- salsa20.XORKeyStream(enc, dhPubRepr[:], state.rNonce[:], state.dsaPubH)
+ var enc []byte
+ if conf.Noise {
+ enc = make([]byte, MTU-xtea.BlockSize-RSize)
+ } else {
+ enc = make([]byte, 32)
+ }
+ copy(enc, dhPubRepr[:])
+ salsa20.XORKeyStream(enc, enc, state.rNonce[:], state.dsaPubH)
data := append(state.rNonce[:], enc...)
data = append(data, idTag(state.Conf.Id, state.rNonce[:])...)
state.conn.Write(data)
// authenticated Peer is ready, then return nil.
func (h *Handshake) Server(data []byte) *Peer {
// R + ENC(H(DSAPub), R, El(CDHPub)) + IDtag
- if len(data) == 48 && h.rNonce == nil {
+ if h.rNonce == nil {
// Generate DH keypair
var dhPubRepr *[32]byte
h.dhPriv, dhPubRepr = dhKeypairGen()
if err := randRead(h.sServer[:]); err != nil {
log.Fatalln("Error reading random for S:", err)
}
- encRs := make([]byte, RSize+SSize)
- salsa20.XORKeyStream(encRs, append(h.rServer[:], h.sServer[:]...), h.rNonce[:], h.key)
+ var encRs []byte
+ if h.Conf.Noise {
+ encRs = make([]byte, MTU-len(encPub)-xtea.BlockSize)
+ } else {
+ encRs = make([]byte, RSize+SSize)
+ }
+ copy(encRs, append(h.rServer[:], h.sServer[:]...))
+ salsa20.XORKeyStream(encRs, encRs, h.rNonce[:], h.key)
// Send that to client
h.conn.Write(append(encPub, append(encRs, idTag(h.Conf.Id, encPub)...)...))
h.LastPing = time.Now()
} else
// ENC(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag
- if len(data) == 120 && h.rClient == nil {
+ if h.rClient == nil {
// Decrypted Rs compare rServer
dec := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
salsa20.XORKeyStream(
}
// Send final answer to client
- enc := make([]byte, RSize)
- salsa20.XORKeyStream(enc, dec[RSize:RSize+RSize], h.rNonceNext(2), h.key)
+ var enc []byte
+ if h.Conf.Noise {
+ enc = make([]byte, MTU-xtea.BlockSize)
+ } else {
+ enc = make([]byte, RSize)
+ }
+ copy(enc, dec[RSize:RSize+RSize])
+ salsa20.XORKeyStream(enc, enc, h.rNonceNext(2), h.key)
h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...))
// Switch peer
peer := newPeer(
+ false,
h.addr,
h.conn,
h.Conf,
- 0,
keyFromSecrets(h.sServer[:], dec[RSize+RSize:RSize+RSize+SSize]))
h.LastPing = time.Now()
return peer
// will be created and used as a transport. If no mutually
// authenticated Peer is ready, then return nil.
func (h *Handshake) Client(data []byte) *Peer {
- switch len(data) {
- case 80: // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag
- if h.key != nil {
- log.Println("Invalid handshake stage from", h.addr)
- return nil
- }
-
+ // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag
+ if h.rServer == nil && h.key == nil {
// Decrypt remote public key and compute shared key
sDHRepr := new([32]byte)
salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH)
}
sign := ed25519.Sign(h.Conf.DSAPriv, h.key[:])
- enc := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
- salsa20.XORKeyStream(enc,
+ var enc []byte
+ if h.Conf.Noise {
+ enc = make([]byte, MTU-xtea.BlockSize)
+ } else {
+ enc = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
+ }
+ copy(enc,
append(h.rServer[:],
append(h.rClient[:],
- append(h.sClient[:], sign[:]...)...)...), h.rNonceNext(1), h.key)
+ append(h.sClient[:], sign[:]...)...)...))
+ salsa20.XORKeyStream(enc, enc, h.rNonceNext(1), h.key)
// Send that to server
h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...))
h.LastPing = time.Now()
- case 16: // ENC(K, R+2, RC) + IDtag
- if h.key == nil {
- log.Println("Invalid handshake stage from", h.addr)
- return nil
- }
-
+ } else
+ // ENC(K, R+2, RC) + IDtag
+ if h.key != nil {
// Decrypt rClient
dec := make([]byte, RSize)
salsa20.XORKeyStream(dec, data[:RSize], h.rNonceNext(2), h.key)
}
// Switch peer
- peer := newPeer(h.addr, h.conn, h.Conf, 1, keyFromSecrets(h.sServer[:], h.sClient[:]))
+ peer := newPeer(
+ true,
+ h.addr,
+ h.conn,
+ h.Conf,
+ keyFromSecrets(h.sServer[:], h.sClient[:]),
+ )
h.LastPing = time.Now()
return peer
- default:
- log.Println("Invalid handshake message from", h.addr)
+ } else {
+ log.Println("Invalid handshake stage from", h.addr)
}
return nil
}
"crypto/subtle"
"encoding/hex"
"errors"
- "io/ioutil"
"log"
- "os"
- "path"
- "strconv"
- "strings"
"sync"
- "time"
- "github.com/agl/ed25519"
"golang.org/x/crypto/xtea"
)
const (
- IDSize = 128 / 8
- RefreshRate = 60 * time.Second
+ IDSize = 128 / 8
)
type PeerId [IDSize]byte
return hex.EncodeToString(id[:])
}
-// Return human readable name of the peer.
-// It equals either to peers/PEER/name file contents or PEER's hex.
-func (id PeerId) MarshalJSON() ([]byte, error) {
- result := id.String()
- if name, err := ioutil.ReadFile(path.Join(PeersPath, result, "name")); err == nil {
- result = strings.TrimRight(string(name), "\n")
+// Decode identification string.
+// It must be 32 hexadecimal characters long.
+func IDDecode(raw string) (*PeerId, error) {
+ if len(raw) != IDSize*2 {
+ return nil, errors.New("ID must be 32 characters long")
+ }
+ idDecoded, err := hex.DecodeString(raw)
+ if err != nil {
+ return nil, errors.New("ID must contain hexadecimal characters only")
}
- return []byte(`"` + result + `"`), nil
+ idP := new([IDSize]byte)
+ copy(idP[:], idDecoded)
+ id := PeerId(*idP)
+ return &id, nil
}
-type PeerConf struct {
- Id *PeerId
- Timeout time.Duration
- NoiseEnable bool
- CPR int
- // This is passphrase verifier
- DSAPub *[ed25519.PublicKeySize]byte
- // This field exists only in dummy configuration on client's side
- DSAPriv *[ed25519.PrivateKeySize]byte
+type CipherCache struct {
+ c map[PeerId]*xtea.Cipher
+ l sync.RWMutex
}
-type cipherCache map[PeerId]*xtea.Cipher
-
-var (
- PeersPath string
- IDsCache cipherCache
- cipherCacheLock sync.RWMutex
- dummyConf *PeerConf
-)
-
-// Initialize (pre-cache) available peers info.
-func PeersInit(path string) {
- PeersPath = path
- IDsCache = make(map[PeerId]*xtea.Cipher)
- go func() {
- for {
- IDsCache.refresh()
- time.Sleep(RefreshRate)
- }
- }()
+func NewCipherCache(peerIds []PeerId) CipherCache {
+ cc := CipherCache{c: make(map[PeerId]*xtea.Cipher, len(peerIds))}
+ cc.Update(peerIds)
+ return cc
}
-// Initialize dummy cache for client-side usage.
-func PeersInitDummy(id *PeerId, conf *PeerConf) {
- IDsCache = make(map[PeerId]*xtea.Cipher)
- cipher, err := xtea.NewCipher(id[:])
- if err != nil {
- panic(err)
- }
- IDsCache[*id] = cipher
- dummyConf = conf
-}
-
-// Refresh IDsCache: remove disappeared keys, add missing ones with
-// initialized ciphers.
-func (cc cipherCache) refresh() {
- dir, err := os.Open(PeersPath)
- if err != nil {
- panic(err)
- }
- peerIds, err := dir.Readdirnames(0)
- if err != nil {
- panic(err)
- }
- available := make(map[PeerId]bool)
+// Remove disappeared keys, add missing ones with initialized ciphers.
+func (cc CipherCache) Update(peerIds []PeerId) {
+ available := make(map[PeerId]struct{})
for _, peerId := range peerIds {
- id, err := IDDecode(peerId)
- if err != nil {
- continue
- }
- available[*id] = true
+ available[peerId] = struct{}{}
}
-
- cipherCacheLock.Lock()
- // Cleanup deleted ones from cache
- for k, _ := range cc {
+ cc.l.Lock()
+ for k, _ := range cc.c {
if _, exists := available[k]; !exists {
- delete(cc, k)
- log.Println("Cleaning key: ", k)
+ log.Println("Cleaning key:", k)
+ delete(cc.c, k)
}
}
- // Add missing ones
for peerId, _ := range available {
- if _, exists := cc[peerId]; !exists {
+ if _, exists := cc.c[peerId]; !exists {
log.Println("Adding key", peerId)
cipher, err := xtea.NewCipher(peerId[:])
if err != nil {
panic(err)
}
- cc[peerId] = cipher
+ cc.c[peerId] = cipher
}
}
- cipherCacheLock.Unlock()
+ cc.l.Unlock()
}
// Try to find peer's identity (that equals to an encryption key)
// by taking first blocksize sized bytes from data at the beginning
// as plaintext and last bytes as cyphertext.
-func (cc cipherCache) Find(data []byte) *PeerId {
+func (cc CipherCache) Find(data []byte) *PeerId {
if len(data) < xtea.BlockSize*2 {
return nil
}
buf := make([]byte, xtea.BlockSize)
- cipherCacheLock.RLock()
- for pid, cipher := range cc {
+ cc.l.RLock()
+ for pid, cipher := range cc.c {
cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
ppid := PeerId(pid)
- cipherCacheLock.RUnlock()
+ cc.l.RUnlock()
return &ppid
}
}
- cipherCacheLock.RUnlock()
+ cc.l.RUnlock()
return nil
}
-
-func readIntFromFile(path string) (int, error) {
- data, err := ioutil.ReadFile(path)
- if err != nil {
- return 0, err
- }
- val, err := strconv.Atoi(strings.TrimRight(string(data), "\n"))
- if err != nil {
- return 0, err
- }
- return val, nil
-}
-
-// Get peer related configuration.
-func (id *PeerId) Conf() *PeerConf {
- if dummyConf != nil {
- return dummyConf
- }
- conf := PeerConf{Id: id, NoiseEnable: false, CPR: 0}
- peerPath := path.Join(PeersPath, id.String())
-
- verPath := path.Join(peerPath, "verifier")
- keyData, err := ioutil.ReadFile(verPath)
- if err != nil {
- log.Println("Unable to read verifier:", verPath)
- return nil
- }
- if len(keyData) < ed25519.PublicKeySize*2 {
- log.Println("Verifier must be 64 hex characters long:", verPath)
- return nil
- }
- keyDecoded, err := hex.DecodeString(string(keyData[:ed25519.PublicKeySize*2]))
- if err != nil {
- log.Println("Unable to decode the key:", err.Error(), verPath)
- return nil
- }
- conf.DSAPub = new([ed25519.PublicKeySize]byte)
- copy(conf.DSAPub[:], keyDecoded)
-
- timeout := TimeoutDefault
- if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil {
- timeout = val
- }
- conf.Timeout = time.Second * time.Duration(timeout)
-
- if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
- conf.NoiseEnable = true
- }
- if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil {
- conf.CPR = val
- }
- return &conf
-}
-
-// Decode identification string.
-// It must be 32 hexadecimal characters long.
-func IDDecode(raw string) (*PeerId, error) {
- if len(raw) != IDSize*2 {
- return nil, errors.New("ID must be 32 characters long")
- }
- idDecoded, err := hex.DecodeString(raw)
- if err != nil {
- return nil, errors.New("ID must contain hexadecimal characters only")
- }
- idP := new([IDSize]byte)
- copy(idP[:], idDecoded)
- id := PeerId(*idP)
- return &id, nil
-}
--- /dev/null
+package govpn
+
+import (
+ "encoding/binary"
+ "io"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "golang.org/x/crypto/poly1305"
+ "golang.org/x/crypto/salsa20"
+ "golang.org/x/crypto/xtea"
+)
+
+const (
+ NonceSize = 8
+ NonceBucketSize = 128
+ TagSize = poly1305.TagSize
+ // S20BS is Salsa20's internal blocksize in bytes
+ S20BS = 64
+ // Maximal amount of bytes transfered with single key (4 GiB)
+ MaxBytesPerKey int64 = 1 << 32
+ // Size of packet's size mark in bytes
+ PktSizeSize = 2
+ // Heartbeat rate, relative to Timeout
+ TimeoutHeartbeat = 4
+ // Minimal valid packet length
+ MinPktLength = 2 + 16 + 8
+)
+
+func newNonceCipher(key *[32]byte) *xtea.Cipher {
+ nonceKey := make([]byte, 16)
+ salsa20.XORKeyStream(
+ nonceKey,
+ make([]byte, 32),
+ make([]byte, xtea.BlockSize),
+ key,
+ )
+ ciph, err := xtea.NewCipher(nonceKey)
+ if err != nil {
+ panic(err)
+ }
+ return ciph
+}
+
+type Peer struct {
+ Addr string
+ Id *PeerId
+ Conn io.Writer
+
+ // Traffic behaviour
+ NoiseEnable bool
+ CPR int
+ CPRCycle time.Duration `json:"-"`
+
+ // Cryptography related
+ Key *[SSize]byte `json:"-"`
+ NonceCipher *xtea.Cipher `json:"-"`
+ nonceRecv uint64
+ nonceLatest uint64
+ nonceOur uint64
+ NonceExpect uint64 `json:"-"`
+ nonceBucket0 map[uint64]struct{}
+ nonceBucket1 map[uint64]struct{}
+ nonceFound0 bool
+ nonceFound1 bool
+ nonceBucketN int32
+
+ // Timers
+ Timeout time.Duration `json:"-"`
+ Established time.Time
+ LastPing time.Time
+ LastSent time.Time
+ willSentCycle time.Time
+
+ // Statistics
+ BytesIn int64
+ BytesOut int64
+ BytesPayloadIn int64
+ BytesPayloadOut int64
+ FramesIn int
+ FramesOut int
+ FramesUnauth int
+ FramesDup int
+ HeartbeatRecv int
+ HeartbeatSent int
+
+ // Receiver
+ BusyR sync.Mutex `json:"-"`
+ bufR []byte
+ tagR *[TagSize]byte
+ keyAuthR *[SSize]byte
+ pktSizeR uint16
+
+ // Transmitter
+ BusyT sync.Mutex `json:"-"`
+ bufT []byte
+ tagT *[TagSize]byte
+ keyAuthT *[SSize]byte
+ frameT []byte
+ now time.Time
+}
+
+func (p *Peer) String() string {
+ return p.Id.String() + ":" + p.Addr
+}
+
+// Zero peer's memory state.
+func (p *Peer) Zero() {
+ p.BusyT.Lock()
+ p.BusyR.Lock()
+ sliceZero(p.Key[:])
+ sliceZero(p.bufR)
+ sliceZero(p.bufT)
+ sliceZero(p.keyAuthR[:])
+ sliceZero(p.keyAuthT[:])
+ p.BusyT.Unlock()
+ p.BusyR.Unlock()
+}
+
+func (p *Peer) NonceExpectation(buf []byte) {
+ binary.BigEndian.PutUint64(buf, p.NonceExpect)
+ p.NonceCipher.Encrypt(buf, buf)
+}
+
+func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[SSize]byte) *Peer {
+ now := time.Now()
+ timeout := conf.Timeout
+
+ cprCycle := cprCycleCalculate(conf.CPR)
+ noiseEnable := conf.Noise
+ if conf.CPR > 0 {
+ noiseEnable = true
+ timeout = cprCycle
+ } else {
+ timeout = timeout / TimeoutHeartbeat
+ }
+
+ peer := Peer{
+ Addr: addr,
+ Id: conf.Id,
+ Conn: conn,
+
+ NoiseEnable: noiseEnable,
+ CPR: conf.CPR,
+ CPRCycle: cprCycle,
+
+ Key: key,
+ NonceCipher: newNonceCipher(key),
+ nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
+ nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
+
+ Timeout: timeout,
+ Established: now,
+ LastPing: now,
+
+ bufR: make([]byte, S20BS+MTU+NonceSize),
+ bufT: make([]byte, S20BS+MTU+NonceSize),
+ tagR: new([TagSize]byte),
+ tagT: new([TagSize]byte),
+ keyAuthR: new([SSize]byte),
+ keyAuthT: new([SSize]byte),
+ }
+ if isClient {
+ peer.nonceOur = 1
+ peer.NonceExpect = 0 + 2
+ } else {
+ peer.nonceOur = 0
+ peer.NonceExpect = 1 + 2
+ }
+ return &peer
+
+}
+
+// Process incoming Ethernet packet.
+// ready channel is TAPListen's synchronization channel used to tell him
+// that he is free to receive new packets. Encrypted and authenticated
+// packets will be sent to remote Peer side immediately.
+func (p *Peer) EthProcess(data []byte) {
+ p.now = time.Now()
+ p.BusyT.Lock()
+
+ // Zero size is a heartbeat packet
+ if len(data) == 0 {
+ // If this heartbeat is necessary
+ if !p.LastSent.Add(p.Timeout).Before(p.now) {
+ p.BusyT.Unlock()
+ return
+ }
+ p.bufT[S20BS+0] = byte(0)
+ p.bufT[S20BS+1] = byte(0)
+ p.HeartbeatSent++
+ } else {
+ // Copy payload to our internal buffer and we are ready to
+ // accept the next one
+ binary.BigEndian.PutUint16(
+ p.bufT[S20BS:S20BS+PktSizeSize],
+ uint16(len(data)),
+ )
+ copy(p.bufT[S20BS+PktSizeSize:], data)
+ p.BytesPayloadOut += int64(len(data))
+ }
+
+ if p.NoiseEnable {
+ p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize]
+ } else {
+ p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize]
+ }
+ p.nonceOur += 2
+ binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
+ p.NonceCipher.Encrypt(
+ p.frameT[len(p.frameT)-NonceSize:],
+ p.frameT[len(p.frameT)-NonceSize:],
+ )
+ for i := 0; i < SSize; i++ {
+ p.bufT[i] = byte(0)
+ }
+ salsa20.XORKeyStream(
+ p.bufT[:S20BS+len(p.frameT)-NonceSize],
+ p.bufT[:S20BS+len(p.frameT)-NonceSize],
+ p.frameT[len(p.frameT)-NonceSize:],
+ p.Key,
+ )
+
+ copy(p.keyAuthT[:], p.bufT[:SSize])
+ poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
+
+ atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize))
+ p.FramesOut++
+
+ if p.CPRCycle != time.Duration(0) {
+ p.willSentCycle = p.LastSent.Add(p.CPRCycle)
+ if p.willSentCycle.After(p.now) {
+ time.Sleep(p.willSentCycle.Sub(p.now))
+ p.now = p.willSentCycle
+ }
+ }
+
+ p.LastSent = p.now
+ p.Conn.Write(append(p.tagT[:], p.frameT...))
+ p.BusyT.Unlock()
+}
+
+func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
+ p.BusyR.Lock()
+ for i := 0; i < SSize; i++ {
+ p.bufR[i] = byte(0)
+ }
+ copy(p.bufR[S20BS:], data[TagSize:])
+ salsa20.XORKeyStream(
+ p.bufR[:S20BS+len(data)-TagSize-NonceSize],
+ p.bufR[:S20BS+len(data)-TagSize-NonceSize],
+ data[len(data)-NonceSize:],
+ p.Key,
+ )
+
+ copy(p.keyAuthR[:], p.bufR[:SSize])
+ copy(p.tagR[:], data[:TagSize])
+ if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) {
+ p.FramesUnauth++
+ p.BusyR.Unlock()
+ return false
+ }
+
+ // Check if received nonce is known to us in either of two buckets.
+ // If yes, then this is ignored duplicate.
+ // Check from the oldest bucket, as in most cases this will result
+ // in constant time check.
+ // If Bucket0 is filled, then it becomes Bucket1.
+ p.NonceCipher.Decrypt(
+ data[len(data)-NonceSize:],
+ data[len(data)-NonceSize:],
+ )
+ p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
+ if reorderable {
+ _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
+ _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
+ if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
+ p.FramesDup++
+ p.BusyR.Unlock()
+ return false
+ }
+ p.nonceBucket0[p.nonceRecv] = struct{}{}
+ p.nonceBucketN++
+ if p.nonceBucketN == NonceBucketSize {
+ p.nonceBucket1 = p.nonceBucket0
+ p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
+ p.nonceBucketN = 0
+ }
+ } else {
+ if p.nonceRecv != p.NonceExpect {
+ p.FramesDup++
+ p.BusyR.Unlock()
+ return false
+ }
+ p.NonceExpect += 2
+ }
+ if p.nonceRecv > p.nonceLatest {
+ p.nonceLatest = p.nonceRecv
+ }
+
+ p.FramesIn++
+ atomic.AddInt64(&p.BytesIn, int64(len(data)))
+ p.LastPing = time.Now()
+ p.pktSizeR = binary.BigEndian.Uint16(p.bufR[S20BS : S20BS+PktSizeSize])
+
+ if p.pktSizeR == 0 {
+ p.HeartbeatRecv++
+ p.BusyR.Unlock()
+ return true
+ }
+ p.BytesPayloadIn += int64(p.pktSizeR)
+ tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR])
+ p.BusyR.Unlock()
+ return true
+}
var (
peer *Peer
plaintext []byte
- ready chan struct{}
ciphertext []byte
peerId *PeerId
conf *PeerConf
)
-type Dummy struct{
+type Dummy struct {
dst *[]byte
}
NoiseEnable: false,
CPR: 0,
}
- peer = newPeer("foo", Dummy{&ciphertext}, conf, 128, new([SSize]byte))
+ peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte))
plaintext = make([]byte, 789)
- ready = make(chan struct{})
- go func() {
- for {
- <-ready
- }
- }()
}
func BenchmarkEnc(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- peer.NonceOur = 128
- peer.EthProcess(plaintext, ready)
+ peer.EthProcess(plaintext)
}
}
func BenchmarkDec(b *testing.B) {
- peer.EthProcess(plaintext, ready)
- peer = newPeer("foo", Dummy{nil}, conf, 128, new([SSize]byte))
+ peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte))
+ peer.EthProcess(plaintext)
+ peer = newPeer(true, "foo", Dummy{nil}, conf, new([SSize]byte))
+ orig := make([]byte, len(ciphertext))
+ copy(orig, ciphertext)
b.ResetTimer()
for i := 0; i < b.N; i++ {
peer.nonceBucket0 = make(map[uint64]struct{}, 1)
peer.nonceBucket1 = make(map[uint64]struct{}, 1)
- if !peer.PktProcess(ciphertext, Dummy{nil}, ready) {
+ copy(ciphertext, orig)
+ if !peer.PktProcess(ciphertext, Dummy{nil}, true) {
b.Fail()
}
}
)
type TAP struct {
- Name string
- dev io.ReadWriter
- buf []byte
- sink chan []byte
- ready chan struct{}
- synced bool
+ Name string
+ Sink chan []byte
+ dev io.ReadWriter
+ buf0 []byte
+ buf1 []byte
+ bufZ bool
}
+var (
+ taps = make(map[string]*TAP)
+)
+
// Return maximal acceptable TAP interface MTU. This is daemon's MTU
// minus nonce, MAC, packet size mark and Ethernet header sizes.
func TAPMaxMTU() int {
return nil, err
}
tap := TAP{
- Name: ifaceName,
- dev: tapRaw,
- buf: make([]byte, maxIfacePktSize),
- sink: make(chan []byte),
- ready: make(chan struct{}),
- synced: false,
+ Name: ifaceName,
+ dev: tapRaw,
+ buf0: make([]byte, maxIfacePktSize),
+ buf1: make([]byte, maxIfacePktSize),
+ Sink: make(chan []byte),
}
go func() {
var n int
var err error
+ var buf []byte
for {
- <-tap.ready
- n, err = tap.dev.Read(tap.buf)
+ if tap.bufZ {
+ buf = tap.buf0
+ } else {
+ buf = tap.buf1
+ }
+ tap.bufZ = !tap.bufZ
+ n, err = tap.dev.Read(buf)
if err != nil {
panic("Reading TAP:" + err.Error())
}
- tap.sink <- tap.buf[:n]
+ tap.Sink <- buf[:n]
}
}()
return &tap, nil
func (t *TAP) Write(data []byte) (n int, err error) {
return t.dev.Write(data)
}
+
+func TAPListen(ifaceName string) (*TAP, error) {
+ tap, exists := taps[ifaceName]
+ if exists {
+ return tap, nil
+ }
+ tap, err := NewTAP(ifaceName)
+ if err != nil {
+ return nil, err
+ }
+ taps[ifaceName] = tap
+ return tap, nil
+}
+++ /dev/null
-/*
-GoVPN -- simple secure free software virtual private network daemon
-Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
-
-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 3 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, see <http://www.gnu.org/licenses/>.
-*/
-
-package govpn
-
-import (
- "encoding/binary"
- "io"
- "time"
-
- "golang.org/x/crypto/poly1305"
- "golang.org/x/crypto/salsa20"
- "golang.org/x/crypto/xtea"
-)
-
-const (
- NonceSize = 8
- NonceBucketSize = 128
- // S20BS is Salsa20's internal blocksize in bytes
- S20BS = 64
- // Maximal amount of bytes transfered with single key (4 GiB)
- MaxBytesPerKey int64 = 1 << 32
- // Size of packet's size mark in bytes
- PktSizeSize = 2
- // Heartbeat rate, relative to Timeout
- TimeoutHeartbeat = 4
-)
-
-type Peer struct {
- Addr string
- Id *PeerId
- Conn io.Writer
-
- // Traffic behaviour
- NoiseEnable bool
- CPR int
- CPRCycle time.Duration `json:"-"`
-
- // Cryptography related
- Key *[SSize]byte `json:"-"`
- NonceOur uint64 `json:"-"`
- NonceRecv uint64 `json:"-"`
- NonceCipher *xtea.Cipher `json:"-"`
- nonceBucket0 map[uint64]struct{}
- nonceBucket1 map[uint64]struct{}
- nonceFound bool
- nonceBucketN int32
-
- // Timers
- Timeout time.Duration `json:"-"`
- Established time.Time
- LastPing time.Time
- LastSent time.Time
- willSentCycle time.Time
-
- // This variables are initialized only once to relief GC
- buf []byte
- tag *[poly1305.TagSize]byte
- keyAuth *[32]byte
- nonceRecv uint64
- frame []byte
- nonce []byte
- pktSize uint64
- size int
- now time.Time
-
- // Statistics
- BytesIn int64
- BytesOut int64
- BytesPayloadIn int64
- BytesPayloadOut int64
- FramesIn int
- FramesOut int
- FramesUnauth int
- FramesDup int
- HeartbeatRecv int
- HeartbeatSent int
-}
-
-func (p *Peer) String() string {
- return p.Id.String() + ":" + p.Addr
-}
-
-// Zero peer's memory state.
-func (p *Peer) Zero() {
- sliceZero(p.Key[:])
- sliceZero(p.tag[:])
- sliceZero(p.keyAuth[:])
- sliceZero(p.buf)
- sliceZero(p.frame)
- sliceZero(p.nonce)
-}
-
-var (
- Emptiness = make([]byte, 1<<14)
- taps = make(map[string]*TAP)
-)
-
-// Create TAP listening goroutine.
-// This function takes required TAP interface name, opens it and allocates
-// a buffer where all frame data will be written, channel where information
-// about number of read bytes is sent to, synchronization channel (external
-// processes tell that read buffer can be used again) and possible channel
-// opening error.
-func TAPListen(ifaceName string, timeout time.Duration, cpr int) (*TAP, chan []byte, chan struct{}, chan struct{}, error) {
- var tap *TAP
- var err error
- tap, exists := taps[ifaceName]
- if !exists {
- tap, err = NewTAP(ifaceName)
- if err != nil {
- return nil, nil, nil, nil, err
- }
- taps[ifaceName] = tap
- }
- sink := make(chan []byte)
- sinkReady := make(chan struct{})
- sinkTerminate := make(chan struct{})
- sinkSkip := make(chan struct{})
-
- go func() {
- cprCycle := cprCycleCalculate(cpr)
- if cprCycle != time.Duration(0) {
- timeout = cprCycle
- } else {
- timeout = timeout / TimeoutHeartbeat
- }
- heartbeat := time.Tick(timeout)
- var pkt []byte
- ListenCycle:
- for {
- select {
- case <-sinkTerminate:
- break ListenCycle
- case <-heartbeat:
- go func() { sink <- make([]byte, 0) }()
- continue
- case <-sinkSkip:
- case <-sinkReady:
- tap.ready <- struct{}{}
- tap.synced = true
- }
- HeartbeatCatched:
- select {
- case <-heartbeat:
- go func() { sink <- make([]byte, 0) }()
- goto HeartbeatCatched
- case <-sinkTerminate:
- break ListenCycle
- case pkt = <-tap.sink:
- tap.synced = false
- sink <- pkt
- }
- }
- close(sink)
- close(sinkReady)
- close(sinkTerminate)
- }()
- if exists && tap.synced {
- sinkSkip <- struct{}{}
- } else {
- sinkReady <- struct{}{}
- }
- return tap, sink, sinkReady, sinkTerminate, nil
-}
-
-func newNonceCipher(key *[32]byte) *xtea.Cipher {
- nonceKey := make([]byte, 16)
- salsa20.XORKeyStream(
- nonceKey,
- make([]byte, 32),
- make([]byte, xtea.BlockSize),
- key,
- )
- ciph, err := xtea.NewCipher(nonceKey)
- if err != nil {
- panic(err)
- }
- return ciph
-}
-
-func cprCycleCalculate(rate int) time.Duration {
- if rate == 0 {
- return time.Duration(0)
- }
- return time.Second / time.Duration(rate*(1<<10)/MTU)
-}
-
-func newPeer(addr string, conn io.Writer, conf *PeerConf, nonce int, key *[SSize]byte) *Peer {
- now := time.Now()
- timeout := conf.Timeout
- cprCycle := cprCycleCalculate(conf.CPR)
- noiseEnable := conf.NoiseEnable
- if conf.CPR > 0 {
- noiseEnable = true
- timeout = cprCycle
- } else {
- timeout = timeout / TimeoutHeartbeat
- }
- peer := Peer{
- Addr: addr,
- Conn: conn,
- Timeout: timeout,
- Established: now,
- LastPing: now,
- Id: conf.Id,
- NoiseEnable: noiseEnable,
- CPR: conf.CPR,
- CPRCycle: cprCycle,
- NonceOur: uint64(nonce),
- NonceRecv: uint64(0),
- nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
- nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
- Key: key,
- NonceCipher: newNonceCipher(key),
- buf: make([]byte, MTU+S20BS),
- tag: new([poly1305.TagSize]byte),
- keyAuth: new([SSize]byte),
- nonce: make([]byte, NonceSize),
- }
- return &peer
-}
-
-// Process incoming UDP packet.
-// ConnListen'es synchronization channel used to tell him that he is
-// free to receive new packets. Authenticated and decrypted packets
-// will be written to the interface immediately (except heartbeat ones).
-func (p *Peer) PktProcess(data []byte, tap io.Writer, ready chan struct{}) bool {
- p.size = len(data)
- copy(p.buf, Emptiness)
- copy(p.tag[:], data[p.size-poly1305.TagSize:])
- copy(p.buf[S20BS:], data[NonceSize:p.size-poly1305.TagSize])
- salsa20.XORKeyStream(
- p.buf[:S20BS+p.size-poly1305.TagSize],
- p.buf[:S20BS+p.size-poly1305.TagSize],
- data[:NonceSize],
- p.Key,
- )
- copy(p.keyAuth[:], p.buf[:SSize])
- if !poly1305.Verify(p.tag, data[:p.size-poly1305.TagSize], p.keyAuth) {
- ready <- struct{}{}
- p.FramesUnauth++
- return false
- }
-
- // Check if received nonce is known to us in either of two buckets.
- // If yes, then this is ignored duplicate.
- // Check from the oldest bucket, as in most cases this will result
- // in constant time check.
- // If Bucket0 is filled, then it becomes Bucket1.
- p.NonceCipher.Decrypt(p.buf, data[:NonceSize])
- ready <- struct{}{}
- p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize])
- if _, p.nonceFound = p.nonceBucket1[p.NonceRecv]; p.nonceFound {
- p.FramesDup++
- return false
- }
- if _, p.nonceFound = p.nonceBucket0[p.NonceRecv]; p.nonceFound {
- p.FramesDup++
- return false
- }
- p.nonceBucket0[p.NonceRecv] = struct{}{}
- p.nonceBucketN++
- if p.nonceBucketN == NonceBucketSize {
- p.nonceBucket1 = p.nonceBucket0
- p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
- p.nonceBucketN = 0
- }
-
- p.FramesIn++
- p.BytesIn += int64(p.size)
- p.LastPing = time.Now()
- p.NonceRecv = p.nonceRecv
- p.pktSize, _ = binary.Uvarint(p.buf[S20BS : S20BS+PktSizeSize])
- if p.pktSize == 0 {
- p.HeartbeatRecv++
- return true
- }
- p.frame = p.buf[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSize]
- p.BytesPayloadIn += int64(p.pktSize)
- tap.Write(p.frame)
- return true
-}
-
-// Process incoming Ethernet packet.
-// ready channel is TAPListen's synchronization channel used to tell him
-// that he is free to receive new packets. Encrypted and authenticated
-// packets will be sent to remote Peer side immediately.
-func (p *Peer) EthProcess(data []byte, ready chan struct{}) {
- p.now = time.Now()
- p.size = len(data)
- // If this heartbeat is necessary
- if p.size == 0 && !p.LastSent.Add(p.Timeout).Before(p.now) {
- return
- }
- copy(p.buf, Emptiness)
- if p.size > 0 {
- copy(p.buf[S20BS+PktSizeSize:], data)
- ready <- struct{}{}
- binary.PutUvarint(p.buf[S20BS:S20BS+PktSizeSize], uint64(p.size))
- p.BytesPayloadOut += int64(p.size)
- } else {
- p.HeartbeatSent++
- }
-
- p.NonceOur += 2
- copy(p.nonce, Emptiness)
- binary.PutUvarint(p.nonce, p.NonceOur)
- p.NonceCipher.Encrypt(p.nonce, p.nonce)
-
- salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key)
- copy(p.buf[S20BS-NonceSize:S20BS], p.nonce)
- copy(p.keyAuth[:], p.buf[:SSize])
- if p.NoiseEnable {
- p.frame = p.buf[S20BS-NonceSize : S20BS+MTU-NonceSize-poly1305.TagSize]
- } else {
- p.frame = p.buf[S20BS-NonceSize : S20BS+PktSizeSize+p.size]
- }
- poly1305.Sum(p.tag, p.frame, p.keyAuth)
-
- p.BytesOut += int64(len(p.frame) + poly1305.TagSize)
- p.FramesOut++
-
- if p.CPRCycle != time.Duration(0) {
- p.willSentCycle = p.LastSent.Add(p.CPRCycle)
- if p.willSentCycle.After(p.now) {
- time.Sleep(p.willSentCycle.Sub(p.now))
- p.now = p.willSentCycle
- }
- }
- p.LastSent = p.now
- p.Conn.Write(append(p.frame, p.tag[:]...))
-}
#!/bin/sh -ex
+[ -n "$SHA256" ] || SHA256=sha256
+
cur=$(pwd)
tmp=$(mktemp -d)
release=$1
rm -fr $tmp/golang.org $tmp/includes
cat > doc/download.texi <<EOF
-@node Prepared tarballs
+@node Tarballs
@section Prepared tarballs
You can obtain releases source code prepared tarballs on
@url{http://www.cypherpunks.ru/govpn/}.
xz -9 govpn-"$release".tar
gpg --detach-sign --sign --local-user F2F59045FFE2F4A1 govpn-"$release".tar.xz
mv $tmp/govpn-"$release".tar.xz $tmp/govpn-"$release".tar.xz.sig $cur/doc/govpn.html/download
+
+tarball=$cur/doc/govpn.html/download/govpn-"$release".tar.xz
+size=$(( $(cat $tarball | wc -c) / 1024 ))
+hash=$($SHA256 $tarball | sed 's/^.*\([0-9a-f]\{64\}\).*$/\1/')
+cat <<EOF
+An entry for documentation:
+@item $release @tab $size KiB
+@tab @url{download/govpn-${release}.tar.xz, link} @url{download/govpn-${release}.tar.xz.sig, sign}
+@tab @code{$hash}
+EOF
+
+cd $cur
+
+cat <<EOF
+Subject: [EN] GoVPN $release release announcement
+
+I am pleased to announce GoVPN $release release availability!
+
+GoVPN is simple free software virtual private network daemon, aimed to
+be reviewable, secure, DPI/censorship-resistant, written on Go.
+
+It uses fast strong passphrase authenticated key agreement protocol with
+augmented zero-knowledge mutual peers authentication (PAKE DH A-EKE).
+Encrypted, authenticated data transport that hides message's length and
+timestamps. Perfect forward secrecy property. Resistance to: offline
+dictionary attacks, replay attacks, client's passphrases compromising
+and dictionary attacks on the server side. Built-in heartbeating,
+rehandshaking, real-time statistics. Ability to work through UDP, TCP
+and HTTP proxies. IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support.
+
+----------------8<-----------------8<-----------------8<----------------
+
+The main improvements for that release are:
+
+$(git cat-file -p $release | sed -n '6,/^.*BEGIN/p' | sed '$d')
+
+----------------8<-----------------8<-----------------8<----------------
+
+GoVPN's home page is: http://govpn.info -> http://www.cypherpunks.ru/govpn/
+also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/
+
+Source code and its signature for that version can be found here:
+
+ http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz ($size KiB)
+ http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz.sig
+
+SHA256 hash: $hash
+GPG key ID: 0xF2F59045FFE2F4A1 GoVPN release signing key
+Fingerprint: D269 9B73 3C41 2068 D8DA 656E F2F5 9045 FFE2 F4A1
+
+Please send questions regarding the use of GoVPN, bug reports and patches
+to mailing list: https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/
+EOF
+
+cat <<EOF
+Subject: [RU] Состоялся релиз GoVPN $release
+
+Я рад сообщить о выходе релиза GoVPN $release!
+
+GoVPN это простой демон виртуальных частных сетей, код которого нацелен
+на лёгкость чтения и анализа, безопасность, устойчивость к DPI/цензуре,
+написан на Go и является свободным программным обеспечением.
+
+Он использует быстрый сильный аутентифицируемый по парольной фразе
+несбалансированный протокол согласования ключей с двусторонней
+аутентификацией сторон (PAKE DH A-EKE). Зашифрованный, аутентифицируемый
+транспортный протокол передачи данных, скрывающий длины сообщений и их
+временные характеристики. Свойство совершенной прямой секретности.
+Устойчивость к: внесетевым (offline) атакам по словарю, атакам
+повторного воспроизведения (replay), компрометации клиентских парольных
+фраз на стороне сервера. Встроенные функции сердцебиения (heartbeat),
+пересогласования ключей, статистика реального времени. Возможность
+работы поверх UDP, TCP и HTTP прокси. Совместимость с IPv4 и IPv6.
+Поддержка GNU/Linux и FreeBSD.
+
+----------------8<-----------------8<-----------------8<----------------
+
+Основные усовершенствования в этом релизе:
+
+$(git cat-file -p $release | sed -n '6,/^.*BEGIN/p' | sed '$d')
+
+----------------8<-----------------8<-----------------8<----------------
+
+Домашняя страница GoVPN: http://govpn.info -> http://www.cypherpunks.ru/govpn/
+также доступна как скрытый сервис Tor: http://vabu56j2ep2rwv3b.onion/govpn/
+
+Исходный код и его подпись для этой версии находится здесь:
+
+ http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz ($size KiB)
+ http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz.sig
+
+SHA256 хэш: $hash
+Идентификатор GPG ключа: 0xF2F59045FFE2F4A1 GoVPN release signing key
+Отпечаток: D269 9B73 3C41 2068 D8DA 656E F2F5 9045 FFE2 F4A1
+
+Пожалуйста все вопросы касающиеся использования GoVPN, отчёты об ошибках
+и патчи отправляйте в govpn-devel почтовую рассылку:
+https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/
+EOF
#!/bin/sh -e
-getrand()
-{
- local size=$1
- dd if=/dev/urandom bs=$size count=1 2>/dev/null | hexdump -ve '"%02x"'
-}
-
[ -n "$1" ] || {
cat <<EOF
Example script for creating new user peer for GoVPN.
-It just creates directory with random peer ID, dummy verifier,
-dummy up.sh executable script and saves username in it.
+It generates random client's identity, ask for passphrase, generates
+verifier and shows you example JSON entry for server configuration.
Usage: $0 <username>
EOF
}
username=$1
-peerid=$(getrand 16)
+peerid=$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | hexdump -ve '"%02x"')
+[ $(echo -n $peerid | wc -c) = 32 ] || peerid=0"$peerid"
umask 077
-mkdir -p peers/$peerid
-echo '0000000000000000000000000000000000000000000000000000000000000000' > peers/$peerid/verifier
-echo $username > peers/$peerid/name
-echo '#!/bin/sh' > peers/$peerid/up.sh
-chmod 700 peers/$peerid/up.sh
-echo Place verifier to peers/$peerid/verifier
+passphrase=$(mktemp)
+$(dirname $0)/storekey.sh $passphrase
+verifier=$(govpn-verifier -id $peerid -key $passphrase)
+rm -f $passphrase
+echo
+
+cat <<EOF
+Your id is: $peerid
+
+Place the following JSON configuration entry on the server's side:
+
+ "$peerid": {
+ "name": "$username",
+ "up": "/path/to/up.sh",
+ "verifier": "$verifier"
+ }
+
+Verifier was generated with:
+
+ $(dirname $0)/storekey.sh /tmp/passphrase
+ govpn-verifier -id $peerid -key /tmp/passphrase
+
+Create up.sh script that will output on the first line TAP interface
+name that must be used for the peer. For example:
+
+ % umask 077
+ % ed /path/to/up.sh
+ a
+ #!/bin/sh
+ echo tap0
+ .
+ wq
+ 20
+ % chmod +x /path/to/up.sh
+EOF