+* When govpn-server opens TAP files, then it won't release them until
+ daemon itself is exited
* Randomize ports usage
install-strip: install
strip $(BINDIR)/govpn-client $(BINDIR)/govpn-server $(BINDIR)/govpn-verifier
+
+dist:
+ ./utils/makedist.sh $(VERSION)
@item
Сервер конфигурируется используя @url{http://yaml.org/, YAML} файл.
+@item
+Возможность использовать syslog для журналирования.
+
@item
Написан на языке @url{https://golang.org/, Go} с простым кодом,
ориентированным на лёгкость чтения и анализа.
@item
Server is configured through the @url{http://yaml.org/, YAML} file.
+@item
+Ability to use syslog for logging.
+
@item
Written on @url{https://golang.org/, Go} programming language with
simple code that can be read and reviewed.
@node Client
@section Client part
-Except for common @ref{Stats, -stats}, @ref{EGD, -egd} options client
-has the following ones:
+Except for common @ref{Stats, -stats}, @ref{EGD, -egd}, @ref{Syslog, -syslog}
+options client has the following ones:
@table @option
Pay attention how to get @ref{Sources, development source code}.
@table @asis
-@item Nonce and identity encryption
- @url{http://www.cix.co.uk/~klockstone/xtea.pdf, XTEA}.
@item Data encryption
@url{http://cr.yp.to/snuffle.html, Salsa20}.
@item Message authentication
@url{http://cr.yp.to/mac.html, Poly1305}.
+@item Nonce and identity obfuscation
+ @url{https://blake2.net/, BLAKE2b-MAC}.
@item Password authenticated key agreement
DH-A-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519}
and @url{http://ed25519.cr.yp.to/, Ed25519}.
@url{http://theory.lcs.mit.edu/~cis/pubs/rivest/fusion.ps,
All-Or-Nothing-Transformed} (based on
@url{http://cseweb.ucsd.edu/~mihir/papers/oaep.html, OAEP} using
- Salsa20 with @url{https://blake2.net/, BLAKE2b-256} based
+ Salsa20 with BLAKE2b-256 based
@url{http://crypto.stanford.edu/~dabo/abstracts/saep.html, SAEP+}
checksums) data with 128-bits of feeded random.
@item Packet overhead
@multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
@headitem Version @tab Size @tab Tarball @tab SHA256 checksum
+@item @ref{Release 5.7, 5.7} @tab 312 KiB
+@tab @url{download/govpn-5.7.tar.xz, link} @url{download/govpn-5.7.tar.xz.sig, sign}
+@tab @code{17a8a223e2d9d4fd537f8de802bc6c72f16ebf8a8c5430e3fbf045c304f9dfec}
+
@item @ref{Release 5.6, 5.6} @tab 311 KiB
@tab @url{download/govpn-5.6.tar.xz, link} @url{download/govpn-5.6.tar.xz.sig, sign}
@tab @code{d46b8f742f1e2bf17236868512f1ea5ad80f59c3bac753a56ce41a1f465282a8}
In this mode each outgoing packet became larger on 4128 bytes and
@ref{Noise, noise} is forcefully enabled. So this is resource hungry mode!
-@strong{Beware}: by default packet serial numbers are still processed
-through the XTEA encryption. It is not required for confidentiality and
-security, but for randomizing some parts of the traffic to make it
-indistinguishable from the noise, for making it more DPI-proof. It
-safely can be disabled, turned off or maybe its keys even can be
-revealed without security and forward secrecy loss.
-
See @code{govpn/cnw} and @code{govpn/aont} packages for details of AONT
and chaffing operations.
* Noise::
* Constant Packet Rate: CPR.
* Encryptionless mode: Encless.
+* Syslog::
* Verifier::
@end menu
@include noise.texi
@include cpr.texi
@include encless.texi
+@include syslog.texi
@include verifier.texi
@verbatiminclude handshake.utxt
-Each handshake message ends with so called @code{IDtag}: it is an XTEA
-encrypted first 64 bits of each message with client's @ref{Identity} as
-a key. It is used to transmit identity and to mark packet as handshake
-message.
+Each handshake message ends with so called @code{IDtag}: it is
+BLAKE2b-MAC of the first 64 bits of the handshake message, with client's
+@ref{Identity} used as a key. It is used to transmit identity and to
+mark packet as handshake message.
If @ref{Noise, noise} is enabled, then data is padded to fill up packet
to MTU's size.
participant Client
participant Server
-== Preparation ==
-Client -> Client : R=rand(64bit)
-Client -> Client : CDHPriv=rand(256bit)
-
-== Interaction ==
Client -> Server : R, enc(H(DSAPub), R, El(CDHPub))
-Server -> Server : SDHPriv=rand(256bit)
-Server -> Server : K=H(DH(SDHPriv, CDHPub))
-Server -> Server : RS=rand(64bit)
-Server -> Server : SS=rand(256bit)
-Server -> Client : enc(H(DSAPub), R+1, El(SDHPub)); enc(K, R, RS+SS)
-Client -> Client : K=H(DH(CDHPriv, SDHPub))
-Client -> Client : RC=rand(64bit); SC=rand(256bit)
+note right
+R=rand(64bit)
+CDHPriv=rand(256bit)
+end note
+
+Server -> Client : enc(H(DSAPub), R+1, El(SDHPub))\nenc(K, R, RS+SS)
+note right
+SDHPriv=rand(256bit)
+K=H(DH(SDHPriv, CDHPub))
+RS=rand(64bit)
+SS=rand(256bit)
+end note
+
Client -> Server : enc(K, R+1, RS+RC+SC+Sign(DSAPriv, K))
-Server -> Server : compare(RS)
-Server -> Server : Verify(DSAPub, Sign(DSAPriv, K), K)
+note right
+K=H(DH(CDHPriv, SDHPub))
+RC=rand(64bit)
+SC=rand(256bit)
+end note
+
Server -> Client : enc(K, R+2, RC)
+note right
+compare(RS)
+compare(RC)
+Verify(DSAPub, Sign(DSAPriv, K), K)
+MasterKey=SS XOR SC
+end note
-== Finalizing ==
-Client -> Client : compare(RC)
-Client -> Client : MasterKey=SS XOR SC
-Server -> Server : MasterKey=SS XOR SC
@enduml
@unnumbered In the media
@itemize
+@item @url{http://www.stargrave.org/GoVPN.html, GoVPN: secure censorship resistant VPN daemon history and implementation decisions}
@item @url{http://habrahabr.ru/company/ivi/blog/256365/, Реализуем безопасный VPN-протокол} (on russian)
@item @url{http://habrahabr.ru/company/ivi/blog/257431/, Реализуем ещё более безопасный VPN-протокол} (on russian)
@item @url{http://www.linuxspace.org/archives/9449, Установка и настройка безопасного VPN-демона GoVPN 3.2} (on russian)
@node Новости
@section Новости
+@node Релиз 5.8
+@subsection Релиз 5.8
+@itemize
+@item Опциональная возможность использовать syslog для журналирования,
+с @url{https://tools.ietf.org/html/rfc5424, RFC 5424}-похожими
+структурированными записями.
+@item Вместо XTEA алгоритма для обфускации nonce используется
+BLAKE2b-MAC. Теперь нешифрованный режим действительно не зависит от
+алгоритмов шифрования.
+@end itemize
+
@node Релиз 5.7
@subsection Релиз 5.7
@itemize
See also this page @ref{Новости, on russian}.
+@node Release 5.8
+@section Release 5.8
+@itemize
+@item Optional ability to use syslog for logging, with
+@url{https://tools.ietf.org/html/rfc5424, RFC 5424}-like
+structured records.
+@item XTEA algorithm is not used anymore for nonce obfuscation, but
+BLAKE2b-MAC instead. Encryptionless mode now really does not depend on
+encryption functions.
+@end itemize
+
@node Release 5.7
@section Release 5.7
@itemize
@node Server
@section Server part
-Except for common @ref{Stats, -stats}, @ref{EGD, -egd} options client
-has the following ones:
+Except for common @ref{Stats, -stats}, @ref{EGD, -egd}, @ref{Syslog, -syslog}
+options server has the following ones:
@table @option
--- /dev/null
+@node Syslog
+@subsection Syslog
+
+You can enable logging to syslog instead of default stdout using
+@option{-syslog} option. All informational messages during the work
+will be sent with @emph{INFO} level.
@section Transport protocol
@verbatim
-TAG || ENCRYPTED || NONCE --> PACKET
- ^ ^ ^
- | | |
- | | +-------------+
- | | |
- | +-------------+ |
- | | |
- +--< AUTH(AUTH_KEY, ENCRYPTED || NONCE)
- ^ ^
- | |
-+------------------------+ |
-| |
-| +---------------+
-| |
-+--< ENCRYPT(KEY, NONCE, PAYLOAD)
- ^ ^
- | |
- | +--< DATA || PAD [|| ZEROS]
- |
- +--< PRP(PRP_KEY, SERIAL)
+ NONCE = 64bit(MAC(MAC_KEY, SERIAL))
+ PAYLOAD = DATA || PAD [|| ZEROS]
+CIPHERTEXT = ENCRYPT(KEY, NONCE, PAYLOAD)
+ TAG = AUTH(AUTH_KEY, CIPHERTEXT || NONCE)
+ MESSAGE = TAG || CIPHERTEXT || NONCE
@end verbatim
@code{SERIAL} is message's serial number. Odds are reserved for
client (to server) messages, evens for server (to client) messages.
-@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's encryption key @code{PRP_KEY} is the first 128-bit of Salsa20's
-output with established common key and zero nonce (message nonces start
-from 1).
+@code{MAC} is BLAKE2b-MAC used to obfuscate @code{SERIAL}. MAC's key
+@code{MAC_KEY} is the first 256-bit of Salsa20's output with established
+common key and zero nonce (message nonces start from 1).
@verbatim
-PRP_KEY = 128bit(ENCRYPT(KEY, 0))
+MAC_KEY = 256bit(ENCRYPT(KEY, 0))
@end verbatim
@code{ENCRYPT} is Salsa20 stream cipher, with established session
In @ref{Encless, encryptionless mode} this scheme is slightly different:
@verbatim
- PACKET = ENCODED || NONCE
+ NONCE = MAC(MAC_KEY, SERIAL)
ENCODED = ENCLESS(DATA || PAD || ZEROS)
- NONCE = PRP(PRP_KEY, SERIAL)
+ PACKET = ENCODED || NONCE
@end verbatim
@code{ENCLESS} is AONT and chaffing function. There is no need in
import (
"bytes"
"crypto/rand"
+ "io"
"testing"
"testing/quick"
)
)
func init() {
- rand.Read(testKey[:])
+ io.ReadFull(rand.Reader, testKey[:])
}
func TestSymmetric(t *testing.T) {
func BenchmarkEncode(b *testing.B) {
data := make([]byte, 128)
- rand.Read(data)
+ io.ReadFull(rand.Reader, data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(testKey, data)
func BenchmarkDecode(b *testing.B) {
data := make([]byte, 128)
- rand.Read(data)
+ io.ReadFull(rand.Reader, data)
encoded, _ := Encode(testKey, data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
encless = flag.Bool("encless", false, "Encryptionless mode")
cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
egdPath = flag.String("egd", "", "Optional path to EGD socket")
+ syslog = flag.Bool("syslog", false, "Enable logging to syslog")
warranty = flag.Bool("warranty", false, "Print warranty information")
conf *govpn.PeerConf
timeout int
firstUpCall bool = true
knownPeers govpn.KnownPeers
- idsCache *govpn.CipherCache
+ idsCache *govpn.MACCache
)
func main() {
Verifier: verifier,
DSAPriv: priv,
}
- idsCache = govpn.NewCipherCache()
+ idsCache = govpn.NewMACCache()
confs := map[govpn.PeerId]*govpn.PeerConf{*verifier.Id: conf}
idsCache.Update(&confs)
log.Println(govpn.VersionGet())
go govpn.StatsProcessor(statsPort, &knownPeers)
}
+ if *syslog {
+ govpn.SyslogEnable()
+ }
+
termSignal := make(chan os.Signal, 1)
signal.Notify(termSignal, os.Interrupt, os.Kill)
}
select {
case <-termSignal:
- log.Fatalln("Finishing")
+ govpn.BothPrintf(`[finish remote="%s"]`, *remoteAddr)
termination <- struct{}{}
break MainCycle
case <-timeouted:
"log"
"net"
"net/http"
+
+ "cypherpunks.ru/govpn"
)
func proxyTCP(timeouted, rehandshaking, termination chan struct{}) {
if err != nil || resp.StatusCode != http.StatusOK {
log.Fatalln("Unexpected response from proxy")
}
- log.Println("Connected to proxy:", *proxyAddr)
+ govpn.Printf(`[proxy-connected remote="%s" addr="%s"]`, *remoteAddr, *proxyAddr)
go handleTCP(conn, timeouted, rehandshaking, termination)
}
if err != nil {
log.Fatalln("Can not connect to address:", err)
}
- log.Println("Connected to TCP:" + *remoteAddr)
+ govpn.Printf(`[connected remote="%s"]`, *remoteAddr)
handleTCP(conn, timeouted, rehandshaking, termination)
}
default:
}
if prev == len(buf) {
- log.Println("Timeouted waiting for the packet")
+ govpn.Printf(`[packet-timeouted remote="%s"]`, *remoteAddr)
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")
+ govpn.Printf(`[connection-timeouted remote="%s"]`, *remoteAddr)
timeouted <- struct{}{}
break HandshakeCycle
}
if peer == nil {
continue
}
- log.Println("Handshake completed")
+ govpn.Printf(`[handshake-completed remote="%s"]`, *remoteAddr)
knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
if firstUpCall {
go govpn.ScriptCall(*upPath, *ifaceName, *remoteAddr)
}
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()
- }()
+ go govpn.PeerTapProcessor(peer, tap, terminator)
break HandshakeCycle
}
if hs != nil {
return
}
- nonceExpectation := make([]byte, govpn.NonceSize)
- peer.NonceExpectation(nonceExpectation)
prev = 0
var i int
TransportCycle:
default:
}
if prev == len(buf) {
- log.Println("Timeouted waiting for the packet")
+ govpn.Printf(`[packet-timeouted remote="%s"]`, *remoteAddr)
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")
+ govpn.Printf(`[connection-timeouted remote="%s"]`, *remoteAddr)
timeouted <- struct{}{}
break TransportCycle
}
if prev < govpn.MinPktLength {
continue
}
- i = bytes.Index(buf[:prev], nonceExpectation)
+ i = bytes.Index(buf[:prev], peer.NonceExpect)
if i == -1 {
continue
}
if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
- log.Println("Unauthenticated packet, dropping connection")
+ govpn.Printf(`[packet-unauthenticated remote="%s"]`, *remoteAddr)
timeouted <- struct{}{}
break TransportCycle
}
if atomic.LoadUint64(&peer.BytesIn)+atomic.LoadUint64(&peer.BytesOut) > govpn.MaxBytesPerKey {
- log.Println("Need rehandshake")
+ govpn.Printf(`[rehandshake-required remote="%s"]`, *remoteAddr)
rehandshaking <- struct{}{}
break TransportCycle
}
- peer.NonceExpectation(nonceExpectation)
copy(buf, buf[i+govpn.NonceSize:prev])
prev = prev - i - govpn.NonceSize
goto CheckMore
if err != nil {
log.Fatalln("Can not listen on UDP:", err)
}
- log.Println("Connected to UDP:" + *remoteAddr)
+ govpn.Printf(`[connected remote="%s"]`, *remoteAddr)
hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
buf := make([]byte, *mtu*2)
conn.SetReadDeadline(time.Now().Add(time.Second))
n, err = conn.Read(buf)
if timeouts == timeout {
- log.Println("Timeouted")
+ govpn.Printf(`[connection-timeouted remote="%s"]`, *remoteAddr)
timeouted <- struct{}{}
break
}
if peer.PktProcess(buf[:n], tap, true) {
timeouts = 0
} else {
- log.Println("Unauthenticated packet")
+ govpn.Printf(`[packet-unauthenticated remote="%s"]`, *remoteAddr)
timeouts++
}
if atomic.LoadUint64(&peer.BytesIn)+atomic.LoadUint64(&peer.BytesOut) > govpn.MaxBytesPerKey {
- log.Println("Need rehandshake")
+ govpn.Printf(`[rehandshake-required remote="%s"]`, *remoteAddr)
rehandshaking <- struct{}{}
break MainCycle
}
continue
}
if idsCache.Find(buf[:n]) == nil {
- log.Println("Invalid identity in handshake packet")
+ govpn.Printf(`[identity-invalid remote="%s"]`, *remoteAddr)
continue
}
timeouts = 0
if peer == nil {
continue
}
- log.Println("Handshake completed")
+ govpn.Printf(`[handshake-completed remote="%s"]`, *remoteAddr)
knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
if firstUpCall {
go govpn.ScriptCall(*upPath, *ifaceName, *remoteAddr)
}
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()
- }()
+ go govpn.PeerTapProcessor(peer, tap, terminator)
}
if terminator != nil {
terminator <- struct{}{}
import (
"bytes"
- "log"
"sync"
- "time"
"cypherpunks.ru/govpn"
)
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, remoteAddr string) (string, error) {
ifaceName := confs[*peerId].Iface
if confs[*peerId].Up != "" {
result, err := govpn.ScriptCall(confs[*peerId].Up, ifaceName, remoteAddr)
if err != nil {
- log.Println("Script", confs[*peerId].Up, "call failed", err)
+ govpn.Printf(`[script-failed bind="%s" path="%s" err="%s"]`, *bindAddr, confs[*peerId].Up, err)
return "", err
}
if ifaceName == "" {
}
}
if ifaceName == "" {
- log.Println("Can not obtain interface name for", *peerId)
+ govpn.Printf(`[tap-failed bind="%s" peer="%s"]`, *bindAddr, *peerId)
}
return ifaceName, nil
}
var (
confs map[govpn.PeerId]*govpn.PeerConf
- idsCache *govpn.CipherCache
+ idsCache *govpn.MACCache
)
func confRead() (*map[govpn.PeerId]*govpn.PeerConf, error) {
pc.MTU = govpn.MTUDefault
}
if pc.MTU > govpn.MTUMax {
- log.Println("MTU value", pc.MTU, "is too high, overriding to", govpn.MTUMax)
+ govpn.Printf(`[mtu-high bind="%s" value="%d" overriden="%d"]`, *bindAddr, pc.MTU, govpn.MTUMax)
pc.MTU = govpn.MTUMax
}
conf := govpn.PeerConf{
func confRefresh() error {
newConfs, err := confRead()
if err != nil {
- log.Println("Unable to parse peers configuration:", err)
+ govpn.Printf(`[conf-parse-failed bind="%s" err="%s"]`, *bindAddr, err)
return err
}
confs = *newConfs
}
func confInit() {
- idsCache = govpn.NewCipherCache()
+ idsCache = govpn.NewMACCache()
if err := confRefresh(); err != nil {
log.Fatalln(err)
}
stats = flag.String("stats", "", "Enable stats retrieving on host:port")
proxy = flag.String("proxy", "", "Enable HTTP proxy on host:port")
egdPath = flag.String("egd", "", "Optional path to EGD socket")
+ syslog = flag.Bool("syslog", false, "Enable logging to syslog")
warranty = flag.Bool("warranty", false, "Print warranty information")
)
govpn.EGDInit(*egdPath)
}
+ if *syslog {
+ govpn.SyslogEnable()
+ }
+
switch *proto {
case "udp":
startUDP()
if *proxy != "" {
go proxyStart()
}
- log.Println("Server started")
+ govpn.BothPrintf(`[started bind="%s"]`, *bindAddr)
var needsDeletion bool
MainCycle:
for {
select {
case <-termSignal:
- log.Println("Terminating")
+ govpn.BothPrintf(`[terminating bind="%s"]`, *bindAddr)
for _, ps := range peers {
govpn.ScriptCall(
confs[*ps.peer.Id].Down,
hsLock.Lock()
for addr, hs := range handshakes {
if hs.LastPing.Add(timeout).Before(now) {
- log.Println("Deleting handshake state", addr)
+ govpn.Printf(`[handshake-delete bind="%s" addr="%s"]`, *bindAddr, addr)
hs.Zero()
delete(handshakes, addr)
}
needsDeletion = ps.peer.LastPing.Add(timeout).Before(now)
ps.peer.BusyR.Unlock()
if needsDeletion {
- log.Println("Deleting peer", ps.peer)
+ govpn.Printf(`[peer-delete bind="%s" peer="%s"]`, *bindAddr, ps.peer)
delete(peers, addr)
delete(knownPeers, addr)
delete(peersById, *ps.peer.Id)
package main
import (
- "log"
"net/http"
+
+ "cypherpunks.ru/govpn"
)
type proxyHandler struct{}
func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
- log.Println("Hijacking failed:", err.Error())
+ govpn.Printf(`[proxy-hijack-failed bind="%s" err="%s"]`, *bindAddr, err)
return
}
conn.Write([]byte("HTTP/1.0 200 OK\n\n"))
}
func proxyStart() {
- log.Println("HTTP proxy listening on:" + *proxy)
+ govpn.BothPrintf(`[proxy-listen bind="%s" addr="%s"]`, *bindAddr, *proxy)
s := &http.Server{
Addr: *proxy,
Handler: proxyHandler{},
}
- log.Println("HTTP proxy result:", s.ListenAndServe())
+ govpn.BothPrintf(`[proxy-finished bind="%s" result="%s"]`, *bindAddr, s.ListenAndServe())
}
if err != nil {
log.Fatalln("Can not listen on TCP:", err)
}
- log.Println("Listening on TCP:" + *bindAddr)
+ govpn.BothPrintf(`[tcp-listen bind="%s"]`, *bindAddr)
go func() {
for {
conn, err := listener.AcceptTCP()
if err != nil {
- log.Println("Error accepting TCP:", err)
+ govpn.Printf(`[tcp-accept-failed bind="%s" err="%s"]`, *bindAddr, err)
continue
}
go handleTCP(conn)
if hs == nil {
conf = confs[*peerId]
if conf == nil {
- log.Println("Can not get peer configuration:", peerId.String())
+ govpn.Printf(
+ `[conf-get-failed bind="%s" peer="%s"]`,
+ *bindAddr, peerId.String(),
+ )
break
}
hs = govpn.NewHandshake(addr, conn, conf)
continue
}
hs.Zero()
- log.Println("Peer handshake finished:", addr, peer.Id.String())
+ govpn.Printf(
+ `[handshake-completed bind="%s" addr="%s" peer="%s"]`,
+ *bindAddr, addr, peerId.String(),
+ )
peersByIdLock.RLock()
addrPrev, exists := peersById[*peer.Id]
peersByIdLock.RUnlock()
tap: tap,
terminator: make(chan struct{}),
}
- go peerReady(*ps)
+ go govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator)
peersByIdLock.Lock()
kpLock.Lock()
delete(peers, addrPrev)
peersLock.Unlock()
peersByIdLock.Unlock()
kpLock.Unlock()
- log.Println("Rehandshake processed:", peer.Id.String())
+ govpn.Printf(
+ `[rehandshake-completed bind="%s" peer="%s"]`,
+ *bindAddr, peerId.String(),
+ )
} else {
ifaceName, err := callUp(peer.Id, peer.Addr)
if err != nil {
}
tap, err = govpn.TAPListen(ifaceName, peer.MTU)
if err != nil {
- log.Println("Unable to create TAP:", err)
+ govpn.Printf(
+ `[tap-failed bind="%s" peer="%s" err="%s"]`,
+ *bindAddr, peerId.String(), err,
+ )
peer = nil
break
}
tap: tap,
terminator: make(chan struct{}, 1),
}
- go peerReady(*ps)
+ go govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator)
peersLock.Lock()
peersByIdLock.Lock()
kpLock.Lock()
peersLock.Unlock()
peersByIdLock.Unlock()
kpLock.Unlock()
- log.Println("Peer created:", peer.Id.String())
+ govpn.Printf(`[peer-created bind="%s" peer="%s"]`, *bindAddr, peerId.String())
}
break
}
return
}
- nonceExpectation := make([]byte, govpn.NonceSize)
- peer.NonceExpectation(nonceExpectation)
prev = 0
var i int
for {
if prev < govpn.MinPktLength {
continue
}
- i = bytes.Index(buf[:prev], nonceExpectation)
+ i = bytes.Index(buf[:prev], peer.NonceExpect)
if i == -1 {
continue
}
if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
- log.Println(
- "Unauthenticated packet, dropping connection",
- addr, peer.Id.String(),
+ govpn.Printf(
+ `[packet-unauthenticated bind="%s" addr="%s" peer="%s"]`,
+ *bindAddr, addr, peer.Id.String(),
)
break
}
- peer.NonceExpectation(nonceExpectation)
copy(buf, buf[i+govpn.NonceSize:prev])
prev = prev - i - govpn.NonceSize
goto CheckMore
if err != nil {
log.Fatalln("Can not listen on UDP:", err)
}
- log.Println("Listening on UDP:" + *bindAddr)
+ govpn.BothPrintf(`[udp-listen bind="%s"]`, *bindAddr)
udpBufs <- make([]byte, govpn.MTUMax)
go func() {
buf = <-udpBufs
n, raddr, err = conn.ReadFromUDP(buf)
if err != nil {
- log.Println("Unexpected error when receiving", err)
+ govpn.Printf(`[receive-failed bind="%s" err="%s"]`, *bindAddr, err)
break
}
addr = raddr.String()
goto Finished
}
- log.Println("Peer handshake finished:", addr, peer.Id.String())
+ govpn.Printf(
+ `[handshake-completed bind="%s" addr="%s" peer="%s"]`,
+ *bindAddr, addr, peerId.String(),
+ )
hs.Zero()
hsLock.Lock()
delete(handshakes, addr)
terminator: make(chan struct{}),
}
go func(ps PeerState) {
- peerReady(ps)
+ govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator)
<-udpBufs
<-udpBufs
}(*ps)
peersLock.Unlock()
peersByIdLock.Unlock()
kpLock.Unlock()
- log.Println("Rehandshake processed:", peer.Id.String())
+ govpn.Printf(
+ `[rehandshake-completed bind="%s" peer="%s"]`,
+ *bindAddr, peer.Id.String(),
+ )
} else {
go func(addr string, peer *govpn.Peer) {
ifaceName, err := callUp(peer.Id, peer.Addr)
}
tap, err := govpn.TAPListen(ifaceName, peer.MTU)
if err != nil {
- log.Println("Unable to create TAP:", err)
+ govpn.Printf(
+ `[tap-failed bind="%s" peer="%s" err="%s"]`,
+ *bindAddr, peer.Id.String(), err,
+ )
return
}
ps = &PeerState{
terminator: make(chan struct{}),
}
go func(ps PeerState) {
- peerReady(ps)
+ govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator)
<-udpBufs
<-udpBufs
}(*ps)
peersLock.Unlock()
peersByIdLock.Unlock()
kpLock.Unlock()
- log.Println("Peer created:", peer.Id.String())
+ govpn.Printf(`[peer-created bind="%s" peer="%s"]`, *bindAddr, peer.Id.String())
}(addr, peer)
}
goto Finished
CheckID:
peerId = idsCache.Find(buf[:n])
if peerId == nil {
- log.Println("Unknown identity from:", addr)
+ govpn.Printf(`[identity-unknown bind="%s" addr="%s"]`, *bindAddr, addr)
goto Finished
}
conf = confs[*peerId]
if conf == nil {
- log.Println("Unable to get peer configuration:", peerId.String())
+ govpn.Printf(
+ `[conf-get-failed bind="%s" peer="%s"]`,
+ *bindAddr, peerId.String(),
+ )
goto Finished
}
hs = govpn.NewHandshake(
"bytes"
"flag"
"fmt"
+ "io"
"log"
"cypherpunks.ru/govpn"
}
if *verifier == "" {
id := new([govpn.IDSize]byte)
- if _, err := govpn.Rand.Read(id[:]); err != nil {
+ if _, err = io.ReadFull(govpn.Rand, id[:]); err != nil {
log.Fatalln(err)
}
pid := govpn.PeerId(*id)
"bytes"
"crypto/rand"
"encoding/binary"
+ "io"
"testing"
"testing/quick"
)
)
func init() {
- rand.Read(testKey[:])
+ io.ReadFull(rand.Reader, testKey[:])
}
func TestSymmetric(t *testing.T) {
func BenchmarkChaff(b *testing.B) {
nonce := make([]byte, 8)
data := make([]byte, 16)
- rand.Read(nonce)
- rand.Read(data)
+ io.ReadFull(rand.Reader, nonce)
+ io.ReadFull(rand.Reader, data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Chaff(testKey, nonce, data)
func BenchmarkWinnow(b *testing.B) {
nonce := make([]byte, 8)
data := make([]byte, 16)
- rand.Read(nonce)
- rand.Read(data)
+ io.ReadFull(rand.Reader, nonce)
+ io.ReadFull(rand.Reader, data)
chaffed := Chaff(testKey, nonce, data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
import (
"crypto/rand"
- "errors"
"io"
"net"
)
if err != nil {
return 0, err
}
+ defer conn.Close()
conn.Write([]byte{0x02, byte(len(b))})
- read, err := conn.Read(b)
- if err != nil {
- conn.Close()
- return read, err
- }
- if read != len(b) {
- conn.Close()
- return read, errors.New("Got less bytes than expected from EGD")
- }
- conn.Close()
- return read, nil
+ return io.ReadFull(conn, b)
}
func EGDInit(path string) {
package govpn
import (
+ "io"
+
"cypherpunks.ru/govpn/aont"
"cypherpunks.ru/govpn/cnw"
)
func EnclessEncode(authKey *[32]byte, nonce, in []byte) ([]byte, error) {
r := new([aont.RSize]byte)
var err error
- if _, err = Rand.Read(r[:]); err != nil {
+ if _, err = io.ReadFull(Rand, r[:]); err != nil {
return nil, err
}
aonted, err := aont.Encode(r, in)
import (
"bytes"
- "crypto/rand"
"encoding/binary"
+ "io"
"testing"
"testing/quick"
)
)
func init() {
- rand.Read(testKey[:])
+ io.ReadFull(Rand, testKey[:])
}
func TestEnclessSymmetric(t *testing.T) {
func BenchmarkEnclessEncode(b *testing.B) {
nonce := make([]byte, 8)
data := make([]byte, 128)
- rand.Read(nonce)
- rand.Read(data)
+ io.ReadFull(Rand, nonce)
+ io.ReadFull(Rand, data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
EnclessEncode(testKey, nonce, data)
func BenchmarkEnclessDecode(b *testing.B) {
nonce := make([]byte, 8)
data := make([]byte, 128)
- rand.Read(nonce)
- rand.Read(data)
+ io.ReadFull(Rand, nonce)
+ io.ReadFull(Rand, data)
encoded, _ := EnclessEncode(testKey, nonce, data)
b.ResetTimer()
for i := 0; i < b.N; i++ {
"github.com/dchest/blake2b"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/salsa20"
- "golang.org/x/crypto/xtea"
)
const (
repr := new([32]byte)
reprFound := false
for !reprFound {
- if _, err := Rand.Read(priv[:]); err != nil {
+ if _, err := io.ReadFull(Rand, priv[:]); err != nil {
log.Fatalln("Error reading random for DH private key:", err)
}
reprFound = extra25519.ScalarBaseMult(pub, repr, priv)
// Generate ID tag from client identification and data.
func idTag(id *PeerId, timeSync int, data []byte) []byte {
- ciph, err := xtea.NewCipher(id[:])
- if err != nil {
- panic(err)
- }
- enc := make([]byte, xtea.BlockSize)
+ enc := make([]byte, 8)
copy(enc, data)
AddTimeSync(timeSync, enc)
- ciph.Encrypt(enc, enc)
+ mac := blake2b.NewMAC(8, id[:])
+ mac.Write(enc)
+ mac.Sum(enc[:0])
return enc
}
state.dhPriv, dhPubRepr = dhKeypairGen()
state.rNonce = new([RSize]byte)
- if _, err := Rand.Read(state.rNonce[:]); err != nil {
+ if _, err := io.ReadFull(Rand, state.rNonce[:]); err != nil {
log.Fatalln("Error reading random for nonce:", err)
}
var enc []byte
if conf.Noise {
- enc = make([]byte, conf.MTU-xtea.BlockSize-RSize)
+ enc = make([]byte, conf.MTU-8-RSize)
} else {
enc = make([]byte, 32)
}
out, err := EnclessDecode(
h.dsaPubH,
h.rNonce[:],
- data[RSize:len(data)-xtea.BlockSize],
+ data[RSize:len(data)-8],
)
if err != nil {
log.Println("Unable to decode packet from", h.addr, err)
}
copy(cDHRepr[:], out)
} else {
- salsa20.XORKeyStream(
- cDHRepr[:],
- data[RSize:RSize+32],
- h.rNonce[:],
- h.dsaPubH,
- )
+ salsa20.XORKeyStream(cDHRepr[:], data[RSize:RSize+32], h.rNonce[:], h.dsaPubH)
}
// Generate DH keypair
// Generate R* and encrypt them
h.rServer = new([RSize]byte)
- if _, err = Rand.Read(h.rServer[:]); err != nil {
+ if _, err = io.ReadFull(Rand, h.rServer[:]); err != nil {
log.Fatalln("Error reading random for R:", err)
}
h.sServer = new([SSize]byte)
- if _, err = Rand.Read(h.sServer[:]); err != nil {
+ if _, err = io.ReadFull(Rand, h.sServer[:]); err != nil {
log.Fatalln("Error reading random for S:", err)
}
var encRs []byte
if h.Conf.Noise && !h.Conf.Encless {
- encRs = make([]byte, h.Conf.MTU-len(encPub)-xtea.BlockSize)
+ encRs = make([]byte, h.Conf.MTU-len(encPub)-8)
} else if h.Conf.Encless {
- encRs = make([]byte, h.Conf.MTU-xtea.BlockSize)
+ encRs = make([]byte, h.Conf.MTU-8)
} else {
encRs = make([]byte, RSize+SSize)
}
dec, err = EnclessDecode(
h.key,
h.rNonceNext(1),
- data[:len(data)-xtea.BlockSize],
+ data[:len(data)-8],
)
if err != nil {
log.Println("Unable to decode packet from", h.addr, err)
// Send final answer to client
var enc []byte
if h.Conf.Noise {
- enc = make([]byte, h.Conf.MTU-xtea.BlockSize)
+ enc = make([]byte, h.Conf.MTU-8)
} else {
enc = make([]byte, RSize)
}
}
copy(sDHRepr[:], tmp[:32])
} else {
- salsa20.XORKeyStream(
- sDHRepr[:],
- data[:32],
- h.rNonceNext(1),
- h.dsaPubH,
- )
+ salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH)
}
// Compute shared key
tmp, err = EnclessDecode(
h.key,
h.rNonce[:],
- data[len(data)/2:len(data)-xtea.BlockSize],
+ data[len(data)/2:len(data)-8],
)
if err != nil {
log.Println("Unable to decode packet from", h.addr, err)
copy(h.sServer[:], tmp[RSize:RSize+SSize])
} else {
decRs := make([]byte, RSize+SSize)
- salsa20.XORKeyStream(
- decRs,
- data[SSize:SSize+RSize+SSize],
- h.rNonce[:],
- h.key,
- )
+ salsa20.XORKeyStream(decRs, data[SSize:SSize+RSize+SSize], h.rNonce[:], h.key)
copy(h.rServer[:], decRs[:RSize])
copy(h.sServer[:], decRs[RSize:])
}
// Generate R* and signature and encrypt them
h.rClient = new([RSize]byte)
- if _, err = Rand.Read(h.rClient[:]); err != nil {
+ if _, err = io.ReadFull(Rand, h.rClient[:]); err != nil {
log.Fatalln("Error reading random for R:", err)
}
h.sClient = new([SSize]byte)
- if _, err = Rand.Read(h.sClient[:]); err != nil {
+ if _, err = io.ReadFull(Rand, h.sClient[:]); err != nil {
log.Fatalln("Error reading random for S:", err)
}
sign := ed25519.Sign(h.Conf.DSAPriv, h.key[:])
var enc []byte
if h.Conf.Noise {
- enc = make([]byte, h.Conf.MTU-xtea.BlockSize)
+ enc = make([]byte, h.Conf.MTU-8)
} else {
enc = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
}
// Decrypt rClient
var dec []byte
if h.Conf.Encless {
- dec, err = EnclessDecode(
- h.key,
- h.rNonceNext(2),
- data[:len(data)-xtea.BlockSize],
- )
+ dec, err = EnclessDecode(h.key, h.rNonceNext(2), data[:len(data)-8])
if err != nil {
log.Println("Unable to decode packet from", h.addr, err)
return nil
"crypto/subtle"
"encoding/base64"
"encoding/binary"
+ "hash"
"log"
"sync"
"time"
- "golang.org/x/crypto/xtea"
+ "github.com/dchest/blake2b"
)
const (
return []byte(`"` + id.String() + `"`), nil
}
-type CipherAndTimeSync struct {
- c *xtea.Cipher
- t int
+type MACAndTimeSync struct {
+ mac hash.Hash
+ ts int
+ l sync.Mutex
}
-type CipherCache struct {
- c map[PeerId]*CipherAndTimeSync
- l sync.RWMutex
+type MACCache struct {
+ cache map[PeerId]*MACAndTimeSync
+ l sync.RWMutex
}
-func NewCipherCache() *CipherCache {
- return &CipherCache{c: make(map[PeerId]*CipherAndTimeSync)}
+func NewMACCache() *MACCache {
+ return &MACCache{cache: make(map[PeerId]*MACAndTimeSync)}
}
-// Remove disappeared keys, add missing ones with initialized ciphers.
-func (cc *CipherCache) Update(peers *map[PeerId]*PeerConf) {
- cc.l.Lock()
- for pid, _ := range cc.c {
+// Remove disappeared keys, add missing ones with initialized MACs.
+func (mc *MACCache) Update(peers *map[PeerId]*PeerConf) {
+ mc.l.Lock()
+ for pid, _ := range mc.cache {
if _, exists := (*peers)[pid]; !exists {
log.Println("Cleaning key:", pid)
- delete(cc.c, pid)
+ delete(mc.cache, pid)
}
}
for pid, pc := range *peers {
- if _, exists := cc.c[pid]; exists {
- cc.c[pid].t = pc.TimeSync
+ if _, exists := mc.cache[pid]; exists {
+ mc.cache[pid].ts = pc.TimeSync
} else {
log.Println("Adding key", pid)
- cipher, err := xtea.NewCipher(pid[:])
- if err != nil {
- panic(err)
+ mc.cache[pid] = &MACAndTimeSync{
+ mac: blake2b.NewMAC(8, pid[:]),
+ ts: pc.TimeSync,
}
- cc.c[pid] = &CipherAndTimeSync{cipher, pc.TimeSync}
}
}
- cc.l.Unlock()
+ mc.l.Unlock()
}
// If timeSync > 0, then XOR timestamp with the data.
}
}
-// Try to find peer's identity (that equals to an encryption key)
+// Try to find peer's identity (that equals to MAC)
// 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 {
- if len(data) < xtea.BlockSize*2 {
+func (mc *MACCache) Find(data []byte) *PeerId {
+ if len(data) < 8*2 {
return nil
}
- buf := make([]byte, xtea.BlockSize)
- cc.l.RLock()
- for pid, ct := range cc.c {
- ct.c.Decrypt(buf, data[len(data)-xtea.BlockSize:])
- AddTimeSync(ct.t, buf)
- if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
+ buf := make([]byte, 8)
+ mc.l.RLock()
+ for pid, mt := range mc.cache {
+ copy(buf, data)
+ AddTimeSync(mt.ts, buf)
+ mt.l.Lock()
+ mt.mac.Reset()
+ mt.mac.Write(buf)
+ mt.mac.Sum(buf[:0])
+ mt.l.Unlock()
+ if subtle.ConstantTimeCompare(buf, data[len(data)-8:]) == 1 {
ppid := PeerId(pid)
- cc.l.RUnlock()
+ mc.l.RUnlock()
return &ppid
}
}
- cc.l.RUnlock()
+ mc.l.RUnlock()
return nil
}
--- /dev/null
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2016 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 (
+ "log"
+ "log/syslog"
+)
+
+var (
+ sysloger *log.Logger
+)
+
+// Enable logging to syslog, instead of default stdout log.
+func SyslogEnable() {
+ var err error
+ sysloger, err = syslog.NewLogger(syslog.LOG_INFO, 0)
+ if err != nil {
+ log.Fatalln(err)
+ }
+}
+
+// Call either syslog-related logger.Printf if SyslogEnabled,
+// default log.Printf otherwise.
+func Printf(f string, v ...interface{}) {
+ if sysloger == nil {
+ log.Printf(f, v...)
+ } else {
+ sysloger.Printf(f, v...)
+ }
+}
+
+// Call both default log.Printf and syslog-related one.
+func BothPrintf(f string, v ...interface{}) {
+ log.Printf(f, v...)
+ if sysloger != nil {
+ sysloger.Printf(f, v...)
+ }
+}
import (
"bytes"
+ "crypto/subtle"
"encoding/binary"
"io"
"log"
"sync/atomic"
"time"
+ "github.com/dchest/blake2b"
"golang.org/x/crypto/poly1305"
"golang.org/x/crypto/salsa20"
- "golang.org/x/crypto/xtea"
)
const (
NonceSize = 8
- NonceBucketSize = 128
+ NonceBucketSize = 256
TagSize = poly1305.TagSize
// S20BS is Salsa20's internal blocksize in bytes
S20BS = 64
PadByte = byte(0x80)
)
-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 newNonces(key *[32]byte, i uint64) chan *[NonceSize]byte {
+ macKey := make([]byte, 32)
+ salsa20.XORKeyStream(macKey, make([]byte, 32), make([]byte, 8), key)
+ mac := blake2b.NewMAC(NonceSize, macKey)
+ nonces := make(chan *[NonceSize]byte, NonceBucketSize*3)
+ go func() {
+ for {
+ buf := new([NonceSize]byte)
+ binary.BigEndian.PutUint64(buf[:], i)
+ mac.Write(buf[:])
+ mac.Sum(buf[:0])
+ nonces <- buf
+ mac.Reset()
+ i += 2
+ }
+ }()
+ return nonces
}
type Peer struct {
Encless bool
MTU int
- // 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
+ key *[SSize]byte `json:"-"`
// Timers
- Timeout time.Duration `json:"-"`
- Established time.Time
- LastPing time.Time
- LastSent time.Time
- willSentCycle time.Time
+ Timeout time.Duration `json:"-"`
+ Established time.Time
+ LastPing time.Time
// Receiver
BusyR sync.Mutex `json:"-"`
keyAuthR *[SSize]byte
pktSizeR int
+ // UDP-related
+ noncesR chan *[NonceSize]byte
+ nonceRecv [NonceSize]byte
+ nonceBucketL map[[NonceSize]byte]struct{}
+ nonceBucketM map[[NonceSize]byte]struct{}
+ nonceBucketH map[[NonceSize]byte]struct{}
+
+ // TCP-related
+ NonceExpect []byte `json:"-"`
+ noncesExpect chan *[NonceSize]byte
+
// Transmitter
BusyT sync.Mutex `json:"-"`
bufT []byte
tagT *[TagSize]byte
keyAuthT *[SSize]byte
frameT []byte
- now time.Time
+ noncesT chan *[NonceSize]byte
}
func (p *Peer) String() string {
func (p *Peer) Zero() {
p.BusyT.Lock()
p.BusyR.Lock()
- SliceZero(p.Key[:])
+ SliceZero(p.key[:])
SliceZero(p.bufR)
SliceZero(p.bufT)
SliceZero(p.keyAuthR[:])
p.BusyR.Unlock()
}
-func (p *Peer) NonceExpectation(buf []byte) {
- binary.BigEndian.PutUint64(buf, p.NonceExpect)
- p.NonceCipher.Encrypt(buf, buf)
-}
-
func cprCycleCalculate(conf *PeerConf) time.Duration {
if conf.CPR == 0 {
return time.Duration(0)
bufSize += EnclessEnlargeSize
noiseEnable = true
}
+
peer := Peer{
Addr: addr,
Id: conf.Id,
Encless: conf.Encless,
MTU: conf.MTU,
- Key: key,
- NonceCipher: newNonceCipher(key),
- nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
- nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
+ key: key,
Timeout: timeout,
Established: now,
keyAuthR: new([SSize]byte),
keyAuthT: new([SSize]byte),
}
+
if isClient {
- peer.nonceOur = 1
- peer.NonceExpect = 0 + 2
+ peer.noncesT = newNonces(peer.key, 1 + 2)
+ peer.noncesR = newNonces(peer.key, 0 + 2)
+ peer.noncesExpect = newNonces(peer.key, 0 + 2)
} else {
- peer.nonceOur = 0
- peer.NonceExpect = 1 + 2
+ peer.noncesT = newNonces(peer.key, 0 + 2)
+ peer.noncesR = newNonces(peer.key, 1 + 2)
+ peer.noncesExpect = newNonces(peer.key, 1 + 2)
}
- return &peer
+ peer.NonceExpect = make([]byte, NonceSize)
+ nonce := <-peer.noncesExpect
+ copy(peer.NonceExpect, nonce[:])
+
+ var i int
+ peer.nonceBucketL = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+ for i = 0; i < NonceBucketSize; i++ {
+ nonce = <-peer.noncesR
+ peer.nonceBucketL[*nonce] = struct{}{}
+ }
+ peer.nonceBucketM = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+ for i = 0; i < NonceBucketSize; i++ {
+ nonce = <-peer.noncesR
+ peer.nonceBucketM[*nonce] = struct{}{}
+ }
+ peer.nonceBucketH = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+ for i = 0; i < NonceBucketSize; i++ {
+ nonce = <-peer.noncesR
+ peer.nonceBucketH[*nonce] = struct{}{}
+ }
+
+ return &peer
}
// Process incoming Ethernet packet.
log.Println("Padded data packet size", len(data)+1, "is bigger than MTU", p.MTU, p)
return
}
- p.now = time.Now()
p.BusyT.Lock()
// Zero size is a heartbeat packet
SliceZero(p.bufT)
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] = PadByte
p.HeartbeatSent++
} else {
} else {
p.frameT = p.bufT[S20BS : S20BS+len(data)+1+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:],
- )
+ copy(p.frameT[len(p.frameT)-NonceSize:], (<-p.noncesT)[:])
var out []byte
if p.Encless {
var err error
out, err = EnclessEncode(
- p.Key,
+ p.key,
p.frameT[len(p.frameT)-NonceSize:],
p.frameT[:len(p.frameT)-NonceSize],
)
p.bufT[:S20BS+len(p.frameT)-NonceSize],
p.bufT[:S20BS+len(p.frameT)-NonceSize],
p.frameT[len(p.frameT)-NonceSize:],
- p.Key,
+ p.key,
)
copy(p.keyAuthT[:], p.bufT[:SSize])
poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
out = append(p.tagT[:], p.frameT...)
}
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(out)
p.BusyT.Unlock()
}
if p.Encless {
var err error
out, err = EnclessDecode(
- p.Key,
+ p.key,
data[len(data)-NonceSize:],
data[:len(data)-NonceSize],
)
p.bufR[:S20BS+len(data)-TagSize-NonceSize],
p.bufR[:S20BS+len(data)-TagSize-NonceSize],
data[len(data)-NonceSize:],
- p.Key,
+ p.key,
)
copy(p.keyAuthR[:], p.bufR[:SSize])
copy(p.tagR[:], data[:TagSize])
out = p.bufR[S20BS : S20BS+len(data)-TagSize-NonceSize]
}
- // 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 {
+ copy(p.nonceRecv[:], data[len(data)-NonceSize:])
+ _, foundL := p.nonceBucketL[p.nonceRecv]
+ _, foundM := p.nonceBucketM[p.nonceRecv]
+ _, foundH := p.nonceBucketH[p.nonceRecv]
+ // If found is none of buckets: either it is too old,
+ // or too new (many packets were lost)
+ if !(foundL || foundM || foundH) {
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
+ // Delete seen nonce
+ if foundL {
+ delete(p.nonceBucketL, p.nonceRecv)
+ }
+ if foundM {
+ delete(p.nonceBucketM, p.nonceRecv)
+ }
+ if foundH {
+ delete(p.nonceBucketH, p.nonceRecv)
+ }
+ // If we are dealing with the latest bucket, create the new one
+ if foundH {
+ p.nonceBucketL, p.nonceBucketM = p.nonceBucketM, p.nonceBucketH
+ p.nonceBucketH = make(map[[NonceSize]byte]struct{})
+ var nonce *[NonceSize]byte
+ for i := 0; i < NonceBucketSize; i++ {
+ nonce = <-p.noncesR
+ p.nonceBucketH[*nonce] = struct{}{}
+ }
}
} else {
- if p.nonceRecv != p.NonceExpect {
+ if subtle.ConstantTimeCompare(data[len(data)-NonceSize:], p.NonceExpect) != 1 {
p.FramesDup++
p.BusyR.Unlock()
return false
}
- p.NonceExpect += 2
- }
- if p.nonceRecv > p.nonceLatest {
- p.nonceLatest = p.nonceRecv
+ copy(p.NonceExpect, (<-p.noncesExpect)[:])
}
p.FramesIn++
p.BusyR.Unlock()
return true
}
+
+func PeerTapProcessor(peer *Peer, tap *TAP, terminator chan struct{}) {
+ var data []byte
+ var now time.Time
+ lastSent := time.Now()
+ heartbeat := time.NewTicker(peer.Timeout)
+ if peer.CPRCycle == time.Duration(0) {
+ RawProcessor:
+ for {
+ select {
+ case <-terminator:
+ break RawProcessor
+ case <-heartbeat.C:
+ now = time.Now()
+ if lastSent.Add(peer.Timeout).Before(now) {
+ peer.EthProcess(nil)
+ lastSent = now
+ }
+ case data = <-tap.Sink:
+ peer.EthProcess(data)
+ lastSent = time.Now()
+ }
+ }
+ } else {
+ CPRProcessor:
+ for {
+ data = nil
+ select {
+ case <-terminator:
+ break CPRProcessor
+ case data = <-tap.Sink:
+ peer.EthProcess(data)
+ default:
+ }
+ if data == nil {
+ peer.EthProcess(nil)
+ }
+ time.Sleep(peer.CPRCycle)
+ }
+ }
+ close(terminator)
+ peer.Zero()
+ heartbeat.Stop()
+}
MTU: MTUDefault,
Timeout: time.Second * time.Duration(TimeoutDefault),
}
- testPeer = newPeer(true, "foo", Dummy{&testCt}, testConf, new([SSize]byte))
testPt = make([]byte, 789)
}
+func testPeerNew() {
+ testPeer = newPeer(true, "foo", Dummy{&testCt}, testConf, new([SSize]byte))
+}
+
func TestTransportSymmetric(t *testing.T) {
- peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
+ testPeerNew()
+ peerd := newPeer(false, "foo", Dummy{nil}, testConf, new([SSize]byte))
f := func(payload []byte) bool {
if len(payload) == 0 {
return true
}
func TestTransportSymmetricNoise(t *testing.T) {
- peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
+ testPeerNew()
+ peerd := newPeer(false, "foo", Dummy{nil}, testConf, new([SSize]byte))
testPeer.NoiseEnable = true
peerd.NoiseEnable = true
f := func(payload []byte) bool {
}
func TestTransportSymmetricEncless(t *testing.T) {
- peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
+ testPeerNew()
+ peerd := newPeer(false, "foo", Dummy{nil}, testConf, new([SSize]byte))
testPeer.Encless = true
testPeer.NoiseEnable = true
peerd.Encless = true
testPeer = newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
orig := make([]byte, len(testCt))
copy(orig, testCt)
+ nonce := new([NonceSize]byte)
+ copy(nonce[:], testCt[len(testCt)-NonceSize:])
b.ResetTimer()
for i := 0; i < b.N; i++ {
- testPeer.nonceBucket0 = make(map[uint64]struct{}, 1)
- testPeer.nonceBucket1 = make(map[uint64]struct{}, 1)
+ testPeer.nonceBucketL = make(map[[NonceSize]byte]struct{}, 1)
+ testPeer.nonceBucketM = make(map[[NonceSize]byte]struct{}, 1)
+ testPeer.nonceBucketH = make(map[[NonceSize]byte]struct{}, 1)
+ testPeer.nonceBucketL[*nonce] = struct{}{}
copy(testCt, orig)
if !testPeer.PktProcess(testCt, Dummy{nil}, true) {
b.Fail()