From: Sergey Matveev Date: Sat, 22 Aug 2015 21:04:35 +0000 (+0300) Subject: Ability to use TCP as a base transport X-Git-Tag: 3.5^2~6 X-Git-Url: http://www.git.cypherpunks.ru/?p=govpn.git;a=commitdiff_plain;h=c145afc6ee1a5de7aa54e24428ff6f2d8e69df5c Ability to use TCP as a base transport Signed-off-by: Sergey Matveev --- diff --git a/doc/developer.texi b/doc/developer.texi index 58c1099..d7a4b07 100644 --- a/doc/developer.texi +++ b/doc/developer.texi @@ -19,9 +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. +26 bytes per packet. Two more bytes for TCP mode. @item Handshake overhead -4 UDP (2 from client, 2 from server) packets, 264 bytes total payload. +4 UDP (2 from client, 2 from server) packets (round-trips for TCP), +264 bytes total payload (8 bytes more for TCP mode). @item Entropy required 832 bits in average on client, 832 bits in average on server side per handshake. diff --git a/doc/keywords.texi b/doc/keywords.texi index a365ce5..8100179 100644 --- a/doc/keywords.texi +++ b/doc/keywords.texi @@ -8,6 +8,6 @@ password proof, PAKE, password, passphrase, password authenticated key exchange, perfect forward secrecy, PFS, MAC, nonce, verifier, rehandshake, heartbeat, replay attack, MiTM, length hiding, timestamps hiding, noise, constant traffic, constant packet rate, CPR, EGD, -entropy, TAP, TAP, VPN, GNU, Linux, FreeBSD, IPv6, dictionary attack, -mutual authentication, simultaneous clients, JSON, HTTP-server, +entropy, UDP, TCP, TAP, TAP, VPN, GNU, Linux, FreeBSD, IPv6, dictionary +attack, mutual authentication, simultaneous clients, JSON, HTTP-server, statistics, PRNG, traffic analysis, Go, golang. diff --git a/doc/netproto.texi b/doc/netproto.texi new file mode 100644 index 0000000..083e833 --- /dev/null +++ b/doc/netproto.texi @@ -0,0 +1,10 @@ +@node Network transport +@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. diff --git a/doc/overview.texi b/doc/overview.texi index 23ba75d..502422a 100644 --- a/doc/overview.texi +++ b/doc/overview.texi @@ -42,10 +42,10 @@ popular Microsoft Windows or Apple OS X support. @itemize @bullet @item Copylefted free software: licensed under -@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+} +@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}. @item Works with @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP} -network interfaces on top of UDP entirely +network interfaces on top of either UDP or TCP entirely. @item @url{https://www.gnu.org/, GNU}/Linux and @url{http://www.freebsd.org/, FreeBSD} support. diff --git a/doc/timeout.texi b/doc/timeout.texi index e750177..0f2e2db 100644 --- a/doc/timeout.texi +++ b/doc/timeout.texi @@ -12,3 +12,7 @@ the time, even if there is no traffic in corresponding TAP interfaces. @strong{Beware}: this consumes traffic. Stale peers and handshake states are cleaned up every timeout period. + +This applies to TCP connections too: relatively much time can pass until +we understand that remote TCP peer is suddenly died and did not +normally terminate connection. diff --git a/doc/transport.texi b/doc/transport.texi index cdbe7ae..26e6d6f 100644 --- a/doc/transport.texi +++ b/doc/transport.texi @@ -2,37 +2,41 @@ @section Transport protocol @verbatim -ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) + +[PktLen] + ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) + AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE)) @end verbatim All transport and handshake messages are indistinguishable from -pseudo random noise. +pseudo random noise, except when using TCP connections. + +@code{PktLen} is used only with TCP connections. It is big-endian +@emph{uin16} length of the whole packet (except PktLen itself). @code{SERIAL} is message's serial number. Odds are reserved for -client(→server) messages, evens for server(→client) messages. +client(->server) messages, evens for server(->client) messages. @code{ENCn} is XTEA block cipher algorithm used here as PRP (pseudo -random permutation) to randomize, obfuscate @code{SERIAL}. Plaintext +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. 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). +function and requires much more code to create PRP from it. + +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. -Encrypted @code{SERIAL} is used as a nonce for @code{DATA} encryption: -encryption key is different during each handshake, so (key, nonce) pair -is always used only once. @code{ENC} is Salsa20 cipher, with established -session @code{KEY} and encrypted @code{SERIAL} used as a nonce. -@code{DATA_SIZE} is @emph{uint16} storing length of the @code{DATA}. +@code{DATA_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}. Next 256 bits -of Salsa20 are ignored. All remaining output is XORed with the data, -encrypting it. +Salsa20 output are used as a one-time key for @code{AUTH}. 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 diff --git a/doc/user.texi b/doc/user.texi index f694d00..8959c74 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -4,7 +4,7 @@ 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 and TAP virtual network interfaces. GoVPN is just a +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. @@ -18,6 +18,7 @@ with @emph{Go 1.5} gives 435 Mbps TCP throughput. * Identity:: * PAKE:: Password Authenticated Key Agreement * Timeout:: +* Network transport:: * MTU:: Maximum Transmission Unit * Stats:: * Noise:: @@ -32,6 +33,7 @@ with @emph{Go 1.5} gives 435 Mbps TCP throughput. @include identity.texi @include pake.texi @include timeout.texi +@include netproto.texi @include mtu.texi @include stats.texi @include noise.texi diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index d11f843..00d8f6a 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -21,6 +21,7 @@ package main import ( "flag" + "io" "log" "net" "os" @@ -32,6 +33,7 @@ import ( var ( remoteAddr = flag.String("remote", "", "Remote server address") + proto = flag.String("proto", "udp", "Protocol to use: udp or tcp") ifaceName = flag.String("iface", "tap0", "TAP network interface") IDRaw = flag.String("id", "", "Client identification") keyPath = flag.String("key", "", "Path to passphrase file") @@ -74,39 +76,18 @@ func main() { } govpn.PeersInitDummy(id, conf) - bind, err := net.ResolveUDPAddr("udp", "0.0.0.0:0") - if err != nil { - log.Fatalln("Can not resolve address:", err) - } - conn, err := net.DialUDP("udp", bind, remote) - if err != nil { - log.Fatalln("Can not listen on UDP:", err) - } - remote, err := net.ResolveUDPAddr("udp", *remoteAddr) - if err != nil { - log.Fatalln("Can not resolve remote address:", err) + var conn io.Writer + var sink chan []byte + var ready chan struct{} + switch *proto { + case "udp": + conn, sink, ready = startUDP() + case "tcp": + conn, sink, ready = startTCP() + default: + log.Fatalln("Unknown protocol specified") } - sink := make(chan []byte) - ready := make(chan struct{}) - go func() { - buf := make([]byte, govpn.MTU) - var n int - var err error - for { - <-ready - conn.SetReadDeadline(time.Now().Add(time.Second)) - n, err = conn.Read(buf) - if err != nil { - // This is needed for ticking the timeouts counter outside - sink <- nil - continue - } - sink <- buf[:n] - } - }() - ready <- struct{}{} - tap, ethSink, ethReady, _, err := govpn.TAPListen( *ifaceName, time.Second*time.Duration(timeout), @@ -121,9 +102,10 @@ func main() { var peer *govpn.Peer var ethPkt []byte var pkt []byte - knownPeers := govpn.KnownPeers(map[string]**govpn.Peer{remote.String(): &peer}) + 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) @@ -138,14 +120,14 @@ func main() { signal.Notify(termSignal, os.Interrupt, os.Kill) log.Println("Starting handshake") - handshake := govpn.HandshakeStart(remote.String(), conn, conf) + handshake := govpn.HandshakeStart(*remoteAddr, conn, conf) MainCycle: for { if peer != nil && (peer.BytesIn+peer.BytesOut) > govpn.MaxBytesPerKey { peer.Zero() peer = nil - handshake = govpn.HandshakeStart(remote.String(), conn, conf) + handshake = govpn.HandshakeStart(*remoteAddr, conn, conf) log.Println("Rehandshaking") } select { diff --git a/src/govpn/cmd/govpn-client/tcp.go b/src/govpn/cmd/govpn-client/tcp.go new file mode 100644 index 0000000..aa868da --- /dev/null +++ b/src/govpn/cmd/govpn-client/tcp.go @@ -0,0 +1,95 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +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 . +*/ + +package main + +import ( + "encoding/binary" + "io" + "log" + "net" + + "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{}) { + 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} + if err != nil { + log.Fatalln("Can not connect TCP:", err) + } + sink := make(chan []byte) + ready := make(chan struct{}) + go func() { + var err error + var n int + var sizeNbuf int + sizeBuf := make([]byte, 2) + var sizeNeed uint16 + var bufN uint16 + buf := make([]byte, govpn.MTU) + for { + <-ready + if sizeNbuf != 2 { + n, err = c.Read(sizeBuf[sizeNbuf:2]) + if err != nil { + break + } + sizeNbuf += n + if sizeNbuf == 2 { + sizeNeed = binary.BigEndian.Uint16(sizeBuf) + if sizeNeed > uint16(govpn.MTU)-2 { + log.Println("Invalid TCP size, skipping") + sizeNbuf = 0 + sink <- nil + continue + } + bufN = 0 + } + } + ReadMore: + if sizeNeed != bufN { + n, err = c.Read(buf[bufN:sizeNeed]) + if err != nil { + break + } + bufN += uint16(n) + goto ReadMore + } + sizeNbuf = 0 + sink <- buf[:sizeNeed] + } + }() + go func() { ready <- struct{}{} }() + return conn, sink, ready +} diff --git a/src/govpn/cmd/govpn-client/udp.go b/src/govpn/cmd/govpn-client/udp.go new file mode 100644 index 0000000..103ed6d --- /dev/null +++ b/src/govpn/cmd/govpn-client/udp.go @@ -0,0 +1,59 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +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 . +*/ + +package main + +import ( + "io" + "log" + "net" + "time" + + "govpn" +) + +func startUDP() (io.Writer, chan []byte, 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) + 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 + } + sink <- buf[:n] + } + }() + ready <- struct{}{} + return conn, sink, ready +} diff --git a/src/govpn/cmd/govpn-server/main.go b/src/govpn/cmd/govpn-server/main.go index 6e25f4c..669a3ab 100644 --- a/src/govpn/cmd/govpn-server/main.go +++ b/src/govpn/cmd/govpn-server/main.go @@ -35,6 +35,7 @@ import ( var ( bindAddr = flag.String("bind", "[::]:1194", "Bind to address") + proto = flag.String("proto", "udp", "Protocol to use: udp or tcp") peersPath = flag.String("peers", "peers", "Path to peers keys directory") stats = flag.String("stats", "", "Enable stats retrieving on host:port") mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") @@ -42,18 +43,10 @@ var ( ) type Pkt struct { - addr string - conn io.Writer - data []byte -} - -type UDPSender struct { - conn *net.UDPConn - addr *net.UDPAddr -} - -func (c UDPSender) Write(data []byte) (int, error) { - return c.conn.WriteToUDP(data, c.addr) + addr string + conn io.Writer + data []byte + ready chan struct{} } type PeerReadyEvent struct { @@ -94,7 +87,6 @@ type EthEvent struct { func main() { flag.Parse() timeout := time.Second * time.Duration(govpn.TimeoutDefault) - var err error log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) govpn.MTU = *mtu @@ -105,35 +97,15 @@ func main() { govpn.EGDInit(*egdPath) } - bind, err := net.ResolveUDPAddr("udp", *bindAddr) - if err != nil { - log.Fatalln("Can not resolve bind address:", err) + var sink chan Pkt + switch *proto { + case "udp": + sink = startUDP() + case "tcp": + sink = startTCP() + default: + log.Fatalln("Unknown protocol specified") } - lconn, err := net.ListenUDP("udp", bind) - if err != nil { - log.Fatalln("Can listen on UDP:", err) - } - - sink := make(chan Pkt) - ready := make(chan struct{}) - go func() { - buf := make([]byte, govpn.MTU) - var n int - var raddr *net.UDPAddr - var err error - for { - <-ready - lconn.SetReadDeadline(time.Now().Add(time.Second)) - n, raddr, err = lconn.ReadFromUDP(buf) - if err != nil { - // This is needed for ticking the timeouts counter outside - sink <- Pkt{} - continue - } - sink <- Pkt{raddr.String(), UDPSender{lconn, raddr}, buf[:n]} - } - }() - ready <- struct{}{} termSignal := make(chan os.Signal, 1) signal.Notify(termSignal, os.Interrupt, os.Kill) @@ -158,6 +130,7 @@ func main() { ethSink := make(chan EthEvent) log.Println(govpn.VersionGet()) + log.Println("Listening on", *proto, *bindAddr) log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) @@ -234,7 +207,7 @@ MainCycle: ethEvent.peer.EthProcess(ethEvent.data, ethEvent.ready) case pkt = <-sink: if pkt.data == nil { - ready <- struct{}{} + pkt.ready <- struct{}{} continue } handshakeProcessForce = false @@ -243,13 +216,13 @@ MainCycle: peerId = govpn.IDsCache.Find(pkt.data) if peerId == nil { log.Println("Unknown identity from", pkt.addr) - ready <- struct{}{} + pkt.ready <- struct{}{} continue } peerConf = peerId.Conf() if peerConf == nil { log.Println("Can not get peer configuration", peerId.String()) - ready <- struct{}{} + pkt.ready <- struct{}{} continue } state, exists = states[pkt.addr] @@ -283,18 +256,18 @@ MainCycle: } } if !handshakeProcessForce { - ready <- struct{}{} + pkt.ready <- struct{}{} } continue } peerState, exists = peers[pkt.addr] if !exists { - ready <- struct{}{} + 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, ready) { + if !peerState.peer.PktProcess(pkt.data, peerState.tap, pkt.ready) { handshakeProcessForce = true goto HandshakeProcess } diff --git a/src/govpn/cmd/govpn-server/tcp.go b/src/govpn/cmd/govpn-server/tcp.go new file mode 100644 index 0000000..85081a6 --- /dev/null +++ b/src/govpn/cmd/govpn-server/tcp.go @@ -0,0 +1,103 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +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 . +*/ + +package main + +import ( + "encoding/binary" + "log" + "net" + + "govpn" +) + +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() chan Pkt { + bind, err := net.ResolveTCPAddr("tcp", *bindAddr) + if err != nil { + log.Fatalln("Can not resolve bind address:", err) + } + listener, err := net.ListenTCP("tcp", bind) + if err != nil { + log.Fatalln("Can not listen on TCP:", err) + } + sink := make(chan Pkt) + go func() { + for { + conn, _ := listener.AcceptTCP() + ready := make(chan struct{}, 1) + go func(conn *net.TCPConn, ready chan struct{}) { + 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) + for { + <-ready + if sizeNbuf != 2 { + n, err = conn.Read(sizeBuf[sizeNbuf:2]) + if err != nil { + break + } + sizeNbuf += n + if sizeNbuf == 2 { + sizeNeed = binary.BigEndian.Uint16(sizeBuf) + if sizeNeed > uint16(govpn.MTU)-2 { + log.Println("Invalid TCP size, skipping") + sizeNbuf = 0 + sink <- Pkt{ready: ready} + continue + } + bufN = 0 + } + } + ReadMore: + if sizeNeed != bufN { + n, err = conn.Read(buf[bufN:sizeNeed]) + if err != nil { + break + } + bufN += uint16(n) + goto ReadMore + } + sizeNbuf = 0 + sink <- Pkt{ + addr, + TCPSender{conn}, + buf[:sizeNeed], + ready, + } + } + }(conn, ready) + ready <- struct{}{} + } + }() + return sink +} diff --git a/src/govpn/cmd/govpn-server/udp.go b/src/govpn/cmd/govpn-server/udp.go new file mode 100644 index 0000000..d5e1296 --- /dev/null +++ b/src/govpn/cmd/govpn-server/udp.go @@ -0,0 +1,72 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +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 . +*/ + +package main + +import ( + "log" + "net" + "time" + + "govpn" +) + +type UDPSender struct { + conn *net.UDPConn + addr *net.UDPAddr +} + +func (c UDPSender) Write(data []byte) (int, error) { + return c.conn.WriteToUDP(data, c.addr) +} + +func startUDP() chan Pkt { + 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) + if err != nil { + log.Fatalln("Can not listen on UDP:", err) + } + sink := make(chan Pkt) + go func() { + buf := make([]byte, govpn.MTU) + var n int + var raddr *net.UDPAddr + var err error + for { + <-ready + lconn.SetReadDeadline(time.Now().Add(time.Second)) + n, raddr, err = lconn.ReadFromUDP(buf) + if err != nil { + sink <- Pkt{ready: ready} + continue + } + sink <- Pkt{ + raddr.String(), + UDPSender{lconn, raddr}, + buf[:n], + ready, + } + } + }() + ready <- struct{}{} + return sink +}