]> Cypherpunks.ru repositories - govpn.git/commitdiff
Merge branch 'develop' 4.0
authorSergey Matveev <stargrave@stargrave.org>
Sat, 19 Sep 2015 20:26:49 +0000 (23:26 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sat, 19 Sep 2015 20:26:49 +0000 (23:26 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
49 files changed:
README
README.RU [new file with mode: 0644]
VERSION
common.mk
doc/about.ru.texi [new file with mode: 0644]
doc/about.texi [new file with mode: 0644]
doc/client.texi
doc/contacts.texi
doc/cpr.texi
doc/developer.texi
doc/download.texi
doc/example.texi
doc/govpn.texi
doc/handshake.texi
doc/identity.texi
doc/installation.texi
doc/integrity.texi
doc/media.texi
doc/netproto.texi
doc/news.texi
doc/pake.texi
doc/server.texi
doc/sources.texi
doc/todo.texi
doc/transport.texi
doc/user.texi
doc/verifier.texi
src/github.com/agl/ed25519
src/govpn/cmd/govpn-client/main.go
src/govpn/cmd/govpn-client/proxy.go
src/govpn/cmd/govpn-client/tcp.go
src/govpn/cmd/govpn-client/udp.go
src/govpn/cmd/govpn-server/common.go [new file with mode: 0644]
src/govpn/cmd/govpn-server/conf.go [new file with mode: 0644]
src/govpn/cmd/govpn-server/main.go
src/govpn/cmd/govpn-server/proxy.go
src/govpn/cmd/govpn-server/tcp.go
src/govpn/cmd/govpn-server/udp.go
src/govpn/common.go
src/govpn/conf.go [new file with mode: 0644]
src/govpn/govpn.go
src/govpn/handshake.go
src/govpn/identify.go
src/govpn/peer.go [new file with mode: 0644]
src/govpn/peer_test.go [moved from src/govpn/transport_test.go with 65% similarity]
src/govpn/tap.go
src/govpn/transport.go [deleted file]
utils/makedist.sh
utils/newclient.sh

diff --git a/README b/README
index 6173be42fcacc5efa8ff620d25c297b79bd89572..7cbbb9470f27afad83764ac341c8ad666167ffff 100644 (file)
--- a/README
+++ b/README
@@ -1,28 +1,25 @@
-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.
diff --git a/README.RU b/README.RU
new file mode 100644 (file)
index 0000000..d6f5bfa
--- /dev/null
+++ b/README.RU
@@ -0,0 +1,31 @@
+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.
diff --git a/VERSION b/VERSION
index 5a958026daa3208cb329c403433beb56abd5c036..5186d07068cfed4c3f4705df79c73e115dd35c43 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.5
+4.0
index 2ae9bb1fe89fff068f08bf8e9f3a7101f889dac8..34fd0b56102c6f8ebd12096a0cc9f6310738bef9 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -36,8 +36,8 @@ install: all doc
        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
diff --git a/doc/about.ru.texi b/doc/about.ru.texi
new file mode 100644 (file)
index 0000000..d70318f
--- /dev/null
@@ -0,0 +1,60 @@
+@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
diff --git a/doc/about.texi b/doc/about.texi
new file mode 100644 (file)
index 0000000..d205a10
--- /dev/null
@@ -0,0 +1,62 @@
+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
index ec816c48f72d32fb76d28bca32cb94f05619a557..bab6a47c911b947592fb105ddd35b51b89b7dfd3 100644 (file)
@@ -1,4 +1,4 @@
-@node Client part
+@node Client
 @section Client part
 
 Except for common @code{-mtu}, @code{-stats}, @code{-egd}
@@ -7,7 +7,8 @@ options client has the following ones:
 @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
@@ -37,7 +38,7 @@ how to enter passphrase from stdin silently and store it in the file.
 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
index 1154d8dd1fb6535d523b001c06ae8de37a18c17a..262accada3697c33adcacc580f5f5fb487b73382 100644 (file)
@@ -1,15 +1,11 @@
 @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/}.
index cd7b48a3f5d0b1bcd384bb484ef4e38512f6f135..5256a81e7f0c565707ce33478b659ed641f40bbe 100644 (file)
@@ -7,4 +7,4 @@ delays other ones.
 
 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.
index 7ccd9270a353fbb1c9dfb46afde4acb120ae7c38..60352254bbbdb391b2f08e17b97e38539e9ac605 100644 (file)
@@ -1,7 +1,7 @@
-@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
@@ -19,10 +19,10 @@ and @url{http://ed25519.cr.yp.to/, Ed25519}.
 @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.
@@ -30,8 +30,8 @@ handshake.
 
 @menu
 * Verifier structure::
-* Transport protocol::
-* Handshake protocol::
+* Transport protocol: Transport.
+* Handshake protocol: Handshake.
 @end menu
 
 @include verifierstruct.texi
index 124ea632da1b5c914cef72f4544648c71f066dfc..96367a1d314be0e86428cbfa79ffe29bccaa854b 100644 (file)
@@ -1,4 +1,4 @@
-@node Prepared tarballs
+@node Tarballs
 @section Prepared tarballs
 
 You can obtain releases source code prepared tarballs from the links below:
@@ -6,6 +6,10 @@ 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}
index a2e417394bae8f72f5ff759130d30780626bd64c..c26284650498e9b2a7e5ff290dc3799bdf0a93e0 100644 (file)
@@ -1,4 +1,4 @@
-@node Example usage
+@node Example
 @section Example usage
 
 Let's assume that there is some insecure link between your computer and
@@ -15,44 +15,38 @@ is 1432.
 @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
@@ -83,7 +77,7 @@ client% ip route add default via 172.16.0.1
 @example
 client% govpn-client \
     -key key.txt \
-    -id 6d4ac605ce8dc37c2f0bf21cb542a713 \
+    -id 906e34b98750c4f686d6c5489508763c \
     -iface tap10 \
     -remote 192.168.0.1:1194 \
     -mtu 1472
@@ -103,7 +97,7 @@ client% ifconfig tap10 inet6 fc00::2/96 mtu 1412 up
 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
index c4cd8c9a82bae4acdc2007f1e638818f427a5f23..c31b59e2006160fbedf043e63c8d32ca226bd8b2 100644 (file)
@@ -4,8 +4,8 @@
 @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}
@@ -22,93 +22,36 @@ A copy of the license is included in the section entitled "Copying conditions".
 @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
index 0b7f4bd840dea447bbaaab0d5e895c26f59165b9..c15e45643857004d2ae4f07b3fe97339fcff7ef8 100644 (file)
@@ -1,4 +1,4 @@
-@node Handshake protocol
+@node Handshake
 @section Handshake protocol
 
 @verbatiminclude handshake.utxt
@@ -6,9 +6,10 @@
 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}:
 
index cb3061f4b0abc11accfed9a1f6e0195990a8ab59..46a6889d2c1fda4041f4c89a1d5c30ffb408ef90 100644 (file)
@@ -7,7 +7,7 @@ to make DPI and deanonymization much harder to success.
 
 @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.
index 3d496e3ead62c6a7fc6af5bd219463e7002958d7..cc5debba80d04a517677876933bf7836a5204199 100644 (file)
@@ -1,6 +1,13 @@
 @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.
@@ -20,8 +27,8 @@ Included required libraries:
 @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:
 
@@ -37,11 +44,11 @@ There is @code{install} target respecting @code{DESTDIR}. It will
 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
index 01badc5e008af03bf8eb4c866aeb73f2d5e4cd15..dc51aafc4f75df0a1bd5f8bd1ef129a3ae9b6b8e 100644 (file)
@@ -1,11 +1,11 @@
-@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
index c69d35aaffb8b0e5c57761e0d69e2314886acd0d..9423086187e0c00df58dff109e2525f8f1b5b4b4 100644 (file)
@@ -1,4 +1,5 @@
-In the media:
+@node Media
+@unnumbered In the media
 
 @itemize @bullet
 @item @url{http://habrahabr.ru/company/ivi/blog/256365/, Реализуем безопасный VPN-протокол} (on russian)
index 083e83359e4ea9d68b4784465be64676b9cb793a..d033656901f090a735b39a268ad49b577969b867 100644 (file)
@@ -1,10 +1,9 @@
-@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.
index 9eacc0f9129eda41e25616d26437ede13076505a..bdb178e977ef5666aa25fdd16cf113518b78a5b6 100644 (file)
@@ -3,9 +3,18 @@
 
 @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.
@@ -96,7 +105,7 @@ JSON of all known connected peers information. Real-time client's
 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
index e96ad1ae8f9784117e99bee5b2a2b8e314afbc82..d2cb77351f7aed884dddae82efdf2807371e4645 100644 (file)
@@ -6,7 +6,7 @@ client-server authentication. Is is secure, but not convenient for some
 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
@@ -15,7 +15,7 @@ decryption to the memory, or full-disk encryption).
 
 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
index e86d86a3b5155f3f8430553084f61e35b1299683..49a09fb990438998a7a939d354e650c800761120 100644 (file)
@@ -1,4 +1,4 @@
-@node Server part
+@node Server
 @section Server part
 
 Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server
@@ -7,75 +7,69 @@ has the following ones:
 @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
index 3b7e02cf613aae84fa5fa9e1ae65252886bd4f3a..40aa499e28a99f795e47f46073326a1e8716d48c 100644 (file)
@@ -1,13 +1,14 @@
-@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
index f8064cfa8902dc4bb01dd2e4c54f0541e63a49b0..f7d00f3102f60cedcf7719c63ca03ccbb9761b94 100644 (file)
@@ -2,9 +2,10 @@
 @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
index 51dc9de50a03eb6bae6ab8d228144ea7eca31221..bfe35013d7de4cbf4676db0e6fa86fcd2778fcd4 100644 (file)
@@ -1,46 +1,61 @@
-@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.
index 0d237a0130aeae9b7ec3d6ecea833172c42e663a..73214da36ef357ceae50702ad118233a694749af 100644 (file)
@@ -1,33 +1,33 @@
-@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
index 6e97b7e6f02f8363c939a0c23ca94aa921d02574..61b1e4479f954082327f98bb77f76accee825385 100644 (file)
@@ -13,8 +13,8 @@ Enter passphrase:[hello world]
 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:
index d2b94fd789ea21d12fac1a4443dd3a3f79cda72c..278e1ec8e8a6e017cd07577924d6766039146ced 160000 (submodule)
@@ -1 +1 @@
-Subproject commit d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
+Subproject commit 278e1ec8e8a6e017cd07577924d6766039146ced
index 7f32b6c1a5af998b003c2ad45caef7b97a22d5bb..6c611e746a1c43a8aed5a8657e88cccb34371b08 100644 (file)
@@ -16,12 +16,11 @@ You should have received a copy of the GNU General Public License
 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"
@@ -47,11 +46,18 @@ var (
        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)
 
@@ -68,50 +74,22 @@ func main() {
        }
 
        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)
@@ -125,65 +103,35 @@ func main() {
        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)
 }
index 678a5ae931f68b49d1fc0f632b57ea2c79c4691f..bde828e289f2e77c1b0d5d36ffc85422754bfda4 100644 (file)
@@ -21,13 +21,12 @@ package main
 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)
@@ -51,9 +50,6 @@ func proxyTCP() (io.Writer, chan []byte, chan struct{}) {
        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)
 }
index 373789af8be5f9c7f64ecf8df320c04bef20528c..1dd11ec39eb4c62789062e5cfb39eec8a5ca8653 100644 (file)
@@ -19,81 +19,151 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 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()
 }
index 103ed6d479c04db0485bd1db4e842ef21ebd789f..9f45f8be51b68f8ccac1d2eb8dd41a832da9884b 100644 (file)
@@ -19,41 +19,104 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 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()
 }
diff --git a/src/govpn/cmd/govpn-server/common.go b/src/govpn/cmd/govpn-server/common.go
new file mode 100644 (file)
index 0000000..4692e52
--- /dev/null
@@ -0,0 +1,61 @@
+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
+}
diff --git a/src/govpn/cmd/govpn-server/conf.go b/src/govpn/cmd/govpn-server/conf.go
new file mode 100644 (file)
index 0000000..4136fab
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+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()
+               }
+       }()
+}
index 37135b9155d09df6314df3db11a7ec1d8ed43602..be4e7ebc6c83345ead1ed24837ce3aa2ce0e1b46 100644 (file)
@@ -16,75 +16,30 @@ You should have received a copy of the GNU General Public License
 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)
@@ -92,22 +47,22 @@ func main() {
        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")
        }
@@ -118,22 +73,6 @@ func main() {
        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)
@@ -144,10 +83,11 @@ func main() {
                go govpn.StatsProcessor(statsPort, &knownPeers)
        }
        if *proxy != "" {
-               go proxyStart(sink)
+               go proxyStart()
        }
        log.Println("Server started")
 
+       var needsDeletion bool
 MainCycle:
        for {
                select {
@@ -155,128 +95,37 @@ MainCycle:
                        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()
                }
        }
 }
index f1e84192bff8f739c189c77f53f9a8ee2eaa50a4..1a9f81461e66526cfadd5d36c47fa979fe1c3c57 100644 (file)
@@ -23,9 +23,7 @@ import (
        "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()
@@ -34,17 +32,14 @@ func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
                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())
 }
index 42467d644ec98feac3bddcdbb379e38667314a09..437b5005aa8ca11b085419e484bf218108941c3a 100644 (file)
@@ -19,24 +19,15 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 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)
@@ -45,62 +36,154 @@ func startTCP(sink chan Pkt) {
        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()
 }
index c2d6e96c2fe732c7064629e3bb02289701976c71..0f5e5551d6bc853ffa3dbe29886434b1ed02d1e0 100644 (file)
@@ -21,7 +21,6 @@ package main
 import (
        "log"
        "net"
-       "time"
 
        "govpn"
 )
@@ -35,37 +34,161 @@ func (c UDPSender) Write(data []byte) (int, error) {
        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{}{}
 }
index fbafd56ab680de6afc61eb50f41890e1ed1babfc..898690d67d75d375164e0c658d38ed0f98e9a873 100644 (file)
@@ -23,6 +23,7 @@ import (
        "os"
        "os/exec"
        "runtime"
+       "time"
 )
 
 const (
@@ -52,13 +53,20 @@ func ScriptCall(path, ifaceName string) ([]byte, error) {
        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)
+}
diff --git a/src/govpn/conf.go b/src/govpn/conf.go
new file mode 100644 (file)
index 0000000..0eff590
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+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:"-"`
+}
index 9a7b1143c97c1daac5fb9f2ef26ccadba3b344ca..996f31ff903db113da307ccd488ac60461116509 100644 (file)
@@ -16,5 +16,5 @@ You should have received a copy of the GNU General Public License
 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
index 3e3d0d62b0417281f4128ecf750f0e5793012555..0aa65bd624e4e9366db2275b3acff5d6fc04ad1d 100644 (file)
@@ -134,7 +134,7 @@ func dhKeyGen(priv, pub *[32]byte) *[32]byte {
 }
 
 // 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,
@@ -162,8 +162,7 @@ func idTag(id *PeerId, data []byte) []byte {
 // 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()
 
@@ -171,8 +170,14 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake {
        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)
@@ -186,7 +191,7 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake {
 // 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()
@@ -218,15 +223,21 @@ func (h *Handshake) Server(data []byte) *Peer {
                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(
@@ -247,16 +258,22 @@ func (h *Handshake) Server(data []byte) *Peer {
                }
 
                // 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
@@ -272,13 +289,8 @@ func (h *Handshake) Server(data []byte) *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)
@@ -305,21 +317,24 @@ func (h *Handshake) Client(data []byte) *Peer {
                }
                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)
@@ -329,11 +344,17 @@ func (h *Handshake) Client(data []byte) *Peer {
                }
 
                // 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
 }
index c7675b6368b48cc62e7eef3180e8515ac1421e1b..a0b7d22f65de59d0840de73f8b4efbb8be6626ca 100644 (file)
@@ -22,22 +22,14 @@ import (
        "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
@@ -46,187 +38,76 @@ func (id PeerId) String() string {
        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
-}
diff --git a/src/govpn/peer.go b/src/govpn/peer.go
new file mode 100644 (file)
index 0000000..1076e74
--- /dev/null
@@ -0,0 +1,316 @@
+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
+}
similarity index 65%
rename from src/govpn/transport_test.go
rename to src/govpn/peer_test.go
index 5809a9f4425a0a12a1edcacb38f276a5f8cf9ee2..9bad8c6960a792c1ca41b766289188aa977dc1a0 100644 (file)
@@ -8,13 +8,12 @@ import (
 var (
        peer       *Peer
        plaintext  []byte
-       ready      chan struct{}
        ciphertext []byte
        peerId     *PeerId
        conf       *PeerConf
 )
 
-type Dummy struct{
+type Dummy struct {
        dst *[]byte
 }
 
@@ -34,32 +33,29 @@ func init() {
                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()
                }
        }
index c5e3bf835cfee5f993f4eeff946c035bd005617e..9c999b41374b2750fa05490331219b388fb88107 100644 (file)
@@ -29,14 +29,18 @@ const (
 )
 
 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 {
@@ -50,23 +54,28 @@ func NewTAP(ifaceName string) (*TAP, error) {
                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
@@ -75,3 +84,16 @@ func NewTAP(ifaceName string) (*TAP, error) {
 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
+}
diff --git a/src/govpn/transport.go b/src/govpn/transport.go
deleted file mode 100644 (file)
index 23f04dd..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
-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[:]...))
-}
index 00ba9ac90bbb3ce36a1682d6cd844414bb92c7dd..10f18d9cdeff86020062ab50d80c45c35552dc12 100755 (executable)
@@ -1,5 +1,7 @@
 #!/bin/sh -ex
 
+[ -n "$SHA256" ] || SHA256=sha256
+
 cur=$(pwd)
 tmp=$(mktemp -d)
 release=$1
@@ -31,7 +33,7 @@ mv $tmp/golang.org src/
 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/}.
@@ -47,3 +49,102 @@ tar cvf govpn-"$release".tar govpn-"$release"
 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
index e741130957a6f30d6c8956988bcd3c3b58e2f940..9e1cb3578faaf71f131245cf2c128b2df3d07c06 100755 (executable)
@@ -1,16 +1,10 @@
 #!/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
@@ -18,11 +12,41 @@ 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