]> Cypherpunks.ru repositories - govpn.git/commitdiff
Ability to use TCP as a base transport
authorSergey Matveev <stargrave@stargrave.org>
Sat, 22 Aug 2015 21:04:35 +0000 (00:04 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 23 Aug 2015 08:24:59 +0000 (11:24 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
13 files changed:
doc/developer.texi
doc/keywords.texi
doc/netproto.texi [new file with mode: 0644]
doc/overview.texi
doc/timeout.texi
doc/transport.texi
doc/user.texi
src/govpn/cmd/govpn-client/main.go
src/govpn/cmd/govpn-client/tcp.go [new file with mode: 0644]
src/govpn/cmd/govpn-client/udp.go [new file with mode: 0644]
src/govpn/cmd/govpn-server/main.go
src/govpn/cmd/govpn-server/tcp.go [new file with mode: 0644]
src/govpn/cmd/govpn-server/udp.go [new file with mode: 0644]

index 58c109919f8f42867f80884015bab39ac74dc228..d7a4b07ea3693f96bb4e8c298e403a3ce3b0cdd7 100644 (file)
@@ -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.
index a365ce5fef119d61fdea85e55ca0f3929156f00c..8100179150cb0a5ed38dec48533dd9989f71589c 100644 (file)
@@ -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 (file)
index 0000000..083e833
--- /dev/null
@@ -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.
index 23ba75d24332396b43a37418312e8b832dce69a1..502422ade039f0a6126feb85fa83f53594979763 100644 (file)
@@ -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.
index e750177b3aa4f6c2e61093cffa0f5dffb746891f..0f2e2dbf43257272a35fe387068aca35d38e345d 100644 (file)
@@ -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.
index cdbe7aecfa47365502a5ffdd0dc811d1c262d90b..26e6d6f59bf7f6f3f97032db0250b6f2398c0166 100644 (file)
@@ -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
index f694d007dccea8040875225ddd4e190d3dcb466d..8959c74c7bafff5e6623d4b1f75bc137a3365cb4 100644 (file)
@@ -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
index d11f84303d9024de86725ded3c1328a92d79540e..00d8f6abdd57c966cbe5ea39049211a34d38f795 100644 (file)
@@ -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 (file)
index 0000000..aa868da
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+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/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 (file)
index 0000000..103ed6d
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+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 (
+       "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
+}
index 6e25f4ce1342478d682f890f270bcffbec4bd858..669a3ab611e55a6cd34c670278a6de98449f1445 100644 (file)
@@ -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 (file)
index 0000000..85081a6
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+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/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 (file)
index 0000000..d5e1296
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+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 (
+       "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
+}