]> Cypherpunks.ru repositories - govpn.git/commitdiff
Per-peer timeout, noncediff, noise, cpr settings
authorSergey Matveev <stargrave@stargrave.org>
Sat, 2 May 2015 11:51:53 +0000 (14:51 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sat, 2 May 2015 13:36:31 +0000 (16:36 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
12 files changed:
cmd/govpn-client/main.go
cmd/govpn-server/main.go
common.go
cpr.go [deleted file]
doc/client.texi
doc/cpr.texi
doc/overview.texi
doc/server.texi
doc/user.texi
identify.go
transport.go
transport_test.go

index 9d99f080d42ea4f1ebbd11fe0ce05234ae9b710a..30ba8e90a76709fdc9556025bb14e7340a18f08b 100644 (file)
@@ -42,7 +42,7 @@ var (
        nonceDiff  = flag.Int("noncediff", 1, "Allow nonce difference")
        timeoutP   = flag.Int("timeout", 60, "Timeout seconds")
        noisy      = flag.Bool("noise", false, "Enable noise appending")
-       cpr        = flag.Int("cpr", 0, "Enable constant KiB/s out traffic rate")
+       cpr        = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
 )
 
 func main() {
@@ -52,13 +52,15 @@ func main() {
        log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
 
        govpn.MTU = *mtu
-       govpn.Timeout = time.Second * time.Duration(timeout)
-       govpn.Noncediff = *nonceDiff
-       govpn.NoiseEnable = *noisy
-       govpn.CPRInit(*cpr)
 
        id := govpn.IDDecode(*IDRaw)
-       govpn.PeersInitDummy(id)
+       govpn.PeersInitDummy(id, govpn.PeerConf{
+               Id:          id,
+               Timeout:     time.Second * time.Duration(timeout),
+               Noncediff:   *nonceDiff,
+               NoiseEnable: *noisy,
+               CPR:         *cpr,
+       })
        key := govpn.KeyRead(*keyPath)
        if id == nil {
                panic("ID is not specified")
@@ -77,7 +79,11 @@ func main() {
                panic(err)
        }
 
-       tap, ethSink, ethReady, _, err := govpn.TAPListen(*ifaceName)
+       tap, ethSink, ethReady, _, err := govpn.TAPListen(
+               *ifaceName,
+               time.Second*time.Duration(timeout),
+               *cpr,
+       )
        if err != nil {
                panic(err)
        }
index ffb6d2325bc507c141d42b9fd88d940a1c7d6889..6697f2b42351229f4711af3a212ba485f3658585 100644 (file)
@@ -37,10 +37,6 @@ var (
        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")
-       nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference")
-       timeoutP  = flag.Int("timeout", 60, "Timeout seconds")
-       noisy     = flag.Bool("noise", false, "Enable noise appending")
-       cpr        = flag.Int("cpr", 0, "Enable constant KiB/s out traffic rate")
 )
 
 type PeerReadyEvent struct {
@@ -57,7 +53,7 @@ type PeerState struct {
 }
 
 func NewPeerState(peer *govpn.Peer, iface string) *PeerState {
-       tap, sink, ready, terminate, err := govpn.TAPListen(iface)
+       tap, sink, ready, terminate, err := govpn.TAPListen(iface, peer.Timeout, peer.CPR)
        if err != nil {
                log.Println("Unable to create Eth", err)
                return nil
@@ -80,15 +76,11 @@ type EthEvent struct {
 
 func main() {
        flag.Parse()
-       timeout := time.Second * time.Duration(*timeoutP)
+       timeout := time.Second * time.Duration(govpn.TimeoutDefault)
        var err error
        log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
 
        govpn.MTU = *mtu
-       govpn.Timeout = timeout
-       govpn.Noncediff = *nonceDiff
-       govpn.NoiseEnable = *noisy
-       govpn.CPRInit(*cpr)
        govpn.PeersInit(*peersPath)
 
        bind, err := net.ResolveUDPAddr("udp", *bindAddr)
index e5ff496fac91da78690202993a476cc8c3961c65..b87133a59a2ca80c6c16182b5a0c998ecd18029d 100644 (file)
--- a/common.go
+++ b/common.go
@@ -25,15 +25,15 @@ import (
        "os"
        "os/exec"
        "runtime"
-       "time"
+)
+
+const (
+       TimeoutDefault = 60
 )
 
 var (
-       MTU         int
-       Timeout     time.Duration
-       Noncediff   int
-       Version     string
-       NoiseEnable bool = false
+       MTU     int
+       Version string
 )
 
 // Call external program/script.
diff --git a/cpr.go b/cpr.go
deleted file mode 100644 (file)
index 1b3c1a4..0000000
--- a/cpr.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package govpn
-
-import (
-       "net"
-       "time"
-)
-
-type UDPCPR net.UDPConn
-
-var (
-       cprCycle  time.Duration
-       cprEnable bool = false
-)
-
-// Initialize Constant Packet Rate. rate is KiB/s.
-func CPRInit(rate int) {
-       if rate <= 0 {
-               return
-       }
-       NoiseEnable = true
-       cprEnable = true
-       cprCycle = time.Second / time.Duration(rate*(1<<10)/MTU)
-       heartbeatPeriod = cprCycle
-}
index 52f6429888f5071121a0b17a298fab72fb4ee22e..3cb89279101cdf747ae8ceac08daf3e513b7bb6f 100644 (file)
@@ -1,24 +1,43 @@
 @node Client part
 @section Client part
 
-Except for common @code{-mtu}, @code{-noncediff}, @code{-timeout},
-@code{-stats}, @code{-noise} options client has the following ones:
+Except for common @code{-mtu}, @code{-stats}, options client has the
+following ones:
 
 @table @code
+
 @item -remote
 Address (@code{host:port} format) of remote server we need to connect to.
+
 @item -iface
 TAP interface name.
+
 @item -id
 Our client's identification (hexadecimal string).
+
 @item -key
 Path to the file with the PSK key.
+
+@item -timeout
+@ref{Timeout} setting in seconds.
+
+@item -noncediff
+Allowable @ref{Nonce difference}.
+
+@item -noise
+Enable @ref{Noise}.
+
+@item -cpr
+Enable @ref{CPR} in KiB/sec.
+
 @item -up
 Optional path to script that will be executed after connection is
 established. Interface name will be given to it as a first argument.
+
 @item -down
 Same as @code{-up} above, but it is executed when connection is lost,
 when we exit.
+
 @end table
 
 Example up-script that calls DHCP client and IPv6 advertisement
index 70572bfeb3f7da0b47d00919b76ae61c4a9078e6..88339dbad43dbbbb1e898d7d39c038dc59a52ac4 100644 (file)
@@ -6,5 +6,5 @@ appearance. In this mode daemon inserts necessary dummy packets and
 delays other ones.
 
 This mode is turned by @code{-cpr} option, where you specify desired
-outgoing traffic rate in KiB/s (kibibytes per second). This option also
+outgoing traffic rate in KiB/sec (kibibytes per second). This option also
 forces using of the @ref{Noise}! It is turned off by default.
index 4616af776186dbb195d4e8d3e319d4d4fdca7c3e..1886f0318e0de5bd61e37fe0757da3c75e9eb7c5 100644 (file)
@@ -6,8 +6,10 @@ on @url{http://golang.org/, Go programming language}.
 
 Reviewability, high 128-bit security margin and
 @url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}
-resistance in mind in free software solution are the main goals
-for that daemon.
+censorship resistance in mind in free software solution are the main
+goals for that daemon. Most modern widespread protocols and their
+implementations in software are too complex to be reviewed, analyzed and
+modified.
 
 State off art cryptography technologies include:
 @url{http://cr.yp.to/snuffle.html, Salsa20} stream encryption,
@@ -28,7 +30,8 @@ one-time keys protects against
 
 Server can work with several clients simultaneously. Each client is
 @strong{identified} by 128-bit key, that does not leak during handshake
-and each client stays @strong{anonymous} for MiTM and DPI.
+and each client stays @strong{anonymous} for MiTM and DPI. All settings
+are applied per-peer separately.
 
 Optional ability to hide payload packets lengths by appending
 @strong{noise} to them during transmission. Ability to generate constant
@@ -60,6 +63,7 @@ network interfaces on top of UDP entirely
 @item Zero knowledge authentication
 @item Built-in rehandshake and heartbeat features
 @item Several simultaneous clients support
+@item Per-client configuration options
 @item Hiding of payload packets length with noise
 @item Hiding of payload packets appearance with constant packet rate traffic
 @item Optional built-in HTTP-server for retrieving information about
index 69fee73defacb1319bdb99e56f2eaa46c0694d78..4fd074c0d391d220a6b1b328994c733e1fd3bbb4 100644 (file)
@@ -1,8 +1,8 @@
 @node Server part
 @section Server part
 
-Except for common @code{-mtu}, @code{-noncediff}, @code{-timeout},
-@code{-stats}, @code{-noise} options server has the following ones:
+Except for common @code{-mtu}, @code{-stats}, options server has the
+following ones:
 
 @table @code
 @item -bind
@@ -11,24 +11,51 @@ Address (@code{host:port} format) we must bind to.
 Path to the directory containing peers information, database.
 @end table
 
-Peers directory must contain subdirectories with the names of client's identities
-in hexadecimal notation. Each of those subdirectories must have
-@strong{key} file with the corresponding authentication key,
-@strong{up.sh} script that executes each time connection with the client
-establishes, optional @code{name} file containing human readable
-client's name and optional @code{down.sh} that executes during
-connection lost.
+Peers directory must contain subdirectories with the names of client's
+identities in hexadecimal notation. Each subdirectory has the following
+files:
 
-@code{up.sh} script @strong{must} print on the first stdout line the
-name of TAP interface. This script can be simple @code{echo tap10},
-maybe more advanced with dynamic interface creation:
+@table @code
 
-@example
-#!/bin/sh
-$tap=$(ifconfig tap create)
-ifconfig $tap inet6 fc00::1/96 mtu 1412 up
-echo $tap
-@end example
+@item key
+@strong{Required}. Contains corresponding authentication PSK key in
+hexadecimal notation.
+
+@item up.sh
+@strong{Required}. up-script executes each time connection with the
+client is established. It's @emph{stdout} output must contain TAP
+interface name on the first string. This script can be simple
+@code{echo tap10}, or maybe more advanced like this:
+    @example
+    #!/bin/sh
+    $tap=$(ifconfig tap create)
+    ifconfig $tap inet6 fc00::1/96 mtu 1412 up
+    echo $tap
+    @end example
+
+@item down.sh
+Optional. Same as @code{up.sh} above, but executes when connection is
+lost.
+
+@item name
+Optional. Contains human readable username. Used to beauty output of
+@ref{Stats}.
+
+@item timeout
+Optional. Contains @ref{Timeout} setting (decimal notation) in seconds.
+Otherwise default minute timeout will be used.
+
+@item noncediff
+Optional. Contains allowable @ref{Nonce difference} setting (decimal
+notation).
+
+@item noise
+Optional. Contains either "1" (enable @ref{Noise} adding), or "0".
+
+@item cpr
+Optional. Contains @ref{CPR} setting (decimal notation) in KiB/sec.
+
+@end table
 
 Each minute server refreshes peers directory contents and adds newly
 appeared identities, deletes an obsolete ones.
index 075f2991bedea3905cdc0ed736645b0eac41cd2d..a77fd8ad6afcba0ee5031eac83d53c7a72987942 100644 (file)
@@ -14,11 +14,11 @@ automate it using up and down shell scripts.
 * Timeout::
 * Nonce difference::
 * MTU::
-* Client part::
-* Server part::
 * Stats::
 * Noise::
 * CPR::
+* Client part::
+* Server part::
 * Example usage::
 @end menu
 
@@ -28,14 +28,14 @@ automate it using up and down shell scripts.
 
 @include mtu.texi
 
-@include client.texi
-
-@include server.texi
-
 @include stats.texi
 
 @include noise.texi
 
 @include cpr.texi
 
+@include client.texi
+
+@include server.texi
+
 @include example.texi
index 6c16da08a2a3c1cc124f0b0995b99142e24aad59..d6a324f0ce3d68c7340df1b948fd64f1f69527a2 100644 (file)
@@ -25,6 +25,7 @@ import (
        "log"
        "os"
        "path"
+       "strconv"
        "strings"
        "sync"
        "time"
@@ -53,12 +54,21 @@ func (id PeerId) MarshalJSON() ([]byte, error) {
        return []byte(`"` + result + `"`), nil
 }
 
+type PeerConf struct {
+       Id          *PeerId
+       Timeout     time.Duration
+       Noncediff   int
+       NoiseEnable bool
+       CPR         int
+}
+
 type cipherCache map[PeerId]*xtea.Cipher
 
 var (
        PeersPath       string
        IDsCache        cipherCache
        cipherCacheLock sync.RWMutex
+       dummyConf       *PeerConf
 )
 
 // Initialize (pre-cache) available peers info.
@@ -73,15 +83,15 @@ func PeersInit(path string) {
        }()
 }
 
-// Initialize dummy cache for client-side usage. It will consist only
-// of single key.
-func PeersInitDummy(id *PeerId) {
+// Initialize dummy cache for client-side usage.
+func PeersInitDummy(id *PeerId, conf PeerConf) {
        IDsCache = make(map[PeerId]*xtea.Cipher)
        cipher, err := xtea.NewCipher(id[:])
        if err != nil {
                panic(err)
        }
        IDsCache[*id] = cipher
+       dummyConf = &conf
 }
 
 // Refresh IDsCache: remove disappeared keys, add missing ones with
@@ -147,6 +157,44 @@ func (cc cipherCache) Find(data []byte) *PeerId {
        return nil
 }
 
+func readIntFromFile(path string) (int, error) {
+       data, err := ioutil.ReadFile(path)
+       if err != nil {
+               return 0, err
+       }
+       val, err := strconv.Atoi(strings.TrimRight(string(data), "\n"))
+       if err != nil {
+               return 0, err
+       }
+       return val, nil
+}
+
+// Get peer related configuration.
+func (id *PeerId) ConfGet() *PeerConf {
+       if dummyConf != nil {
+               return dummyConf
+       }
+       conf := PeerConf{Id: id, Noncediff: 1, NoiseEnable: false, CPR: 0}
+       peerPath := path.Join(PeersPath, id.String())
+
+       timeout := TimeoutDefault
+       if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil {
+               timeout = val
+       }
+       conf.Timeout = time.Second * time.Duration(timeout)
+
+       if val, err := readIntFromFile(path.Join(peerPath, "noncediff")); err == nil {
+               conf.Noncediff = val
+       }
+       if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
+               conf.NoiseEnable = true
+       }
+       if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil {
+               conf.CPR = val
+       }
+       return &conf
+}
+
 // Decode identification string.
 // It must be 32 hexadecimal characters long.
 // If it is not the valid one, then return nil.
index 6065c5eb219f3411664eded9d1f8965b78d2f2aa..f13c50349276977da1fb07c328cd4f562421f6cb 100644 (file)
@@ -49,23 +49,38 @@ type UDPPkt struct {
 }
 
 type Peer struct {
-       Addr            *net.UDPAddr
-       Id              PeerId
-       Key             *[KeySize]byte `json:"-"`
-       NonceOur        uint64         `json:"-"`
-       NonceRecv       uint64         `json:"-"`
-       NonceCipher     *xtea.Cipher   `json:"-"`
-       Established     time.Time
-       LastPing        time.Time
-       LastSent        time.Time
-       willSentCycle   time.Time
-       buf             []byte
-       tag             *[poly1305.TagSize]byte
-       keyAuth         *[KeySize]byte
-       nonceRecv       uint64
-       frame           []byte
-       nonce           []byte
-       pktSize         uint64
+       Addr *net.UDPAddr
+       Id   PeerId
+
+       // Traffic behaviour
+       NoiseEnable bool
+       CPR         int
+       CPRCycle    time.Duration `json:"-"`
+
+       // Cryptography related
+       Key         *[KeySize]byte `json:"-"`
+       Noncediff   int
+       NonceOur    uint64       `json:"-"`
+       NonceRecv   uint64       `json:"-"`
+       NonceCipher *xtea.Cipher `json:"-"`
+
+       // Timers
+       Timeout       time.Duration `json:"-"`
+       Established   time.Time
+       LastPing      time.Time
+       LastSent      time.Time
+       willSentCycle time.Time
+
+       // This variables are initialized only once to relief GC
+       buf       []byte
+       tag       *[poly1305.TagSize]byte
+       keyAuth   *[KeySize]byte
+       nonceRecv uint64
+       frame     []byte
+       nonce     []byte
+       pktSize   uint64
+
+       // Statistics
        BytesIn         int64
        BytesOut        int64
        BytesPayloadIn  int64
@@ -93,25 +108,17 @@ func (p *Peer) Zero() {
 }
 
 var (
-       Emptiness       = make([]byte, 1<<14)
-       taps            = make(map[string]*TAP)
-       heartbeatPeriod time.Duration
+       Emptiness = make([]byte, 1<<14)
+       taps      = make(map[string]*TAP)
 )
 
-func heartbeatPeriodGet() time.Duration {
-       if heartbeatPeriod == time.Duration(0) {
-               heartbeatPeriod = Timeout / TimeoutHeartbeat
-       }
-       return heartbeatPeriod
-}
-
 // Create TAP listening goroutine.
 // This function takes required TAP interface name, opens it and allocates
 // a buffer where all frame data will be written, channel where information
 // about number of read bytes is sent to, synchronization channel (external
 // processes tell that read buffer can be used again) and possible channel
 // opening error.
-func TAPListen(ifaceName string) (*TAP, chan []byte, chan struct{}, chan struct{}, error) {
+func TAPListen(ifaceName string, timeout time.Duration, cpr int) (*TAP, chan []byte, chan struct{}, chan struct{}, error) {
        var tap *TAP
        var err error
        tap, exists := taps[ifaceName]
@@ -128,7 +135,13 @@ func TAPListen(ifaceName string) (*TAP, chan []byte, chan struct{}, chan struct{
        sinkSkip := make(chan struct{})
 
        go func() {
-               heartbeat := time.Tick(heartbeatPeriodGet())
+               cprCycle := cprCycleCalculate(cpr)
+               if cprCycle != time.Duration(0) {
+                       timeout = cprCycle
+               } else {
+                       timeout = timeout / TimeoutHeartbeat
+               }
+               heartbeat := time.Tick(timeout)
                var pkt []byte
        ListenCycle:
                for {
@@ -211,15 +224,37 @@ func newNonceCipher(key *[KeySize]byte) *xtea.Cipher {
        return ciph
 }
 
+func cprCycleCalculate(rate int) time.Duration {
+       if rate == 0 {
+               return time.Duration(0)
+       }
+       return time.Second / time.Duration(rate*(1<<10)/MTU)
+}
+
 func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer {
        now := time.Now()
+       conf := id.ConfGet()
+       timeout := conf.Timeout
+       cprCycle := cprCycleCalculate(conf.CPR)
+       noiseEnable := conf.NoiseEnable
+       if conf.CPR > 0 {
+               noiseEnable = true
+               timeout = cprCycle
+       } else {
+               timeout = timeout / TimeoutHeartbeat
+       }
        peer := Peer{
                Addr:        addr,
+               Timeout:     timeout,
                Established: now,
                LastPing:    now,
                Id:          id,
-               NonceOur:    uint64(Noncediff + nonce),
-               NonceRecv:   uint64(Noncediff + 0),
+               NoiseEnable: noiseEnable,
+               CPR:         conf.CPR,
+               CPRCycle:    cprCycle,
+               Noncediff:   conf.Noncediff,
+               NonceOur:    uint64(conf.Noncediff + nonce),
+               NonceRecv:   uint64(conf.Noncediff + 0),
                Key:         key,
                NonceCipher: newNonceCipher(key),
                buf:         make([]byte, MTU+S20BS),
@@ -254,7 +289,7 @@ func (p *Peer) UDPProcess(udpPkt []byte, tap io.Writer, ready chan struct{}) boo
        }
        p.NonceCipher.Decrypt(p.buf, udpPkt[:NonceSize])
        p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize])
-       if int(p.NonceRecv)-Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-Noncediff {
+       if int(p.NonceRecv)-p.Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-p.Noncediff {
                ready <- struct{}{}
                p.FramesDup++
                return false
@@ -288,7 +323,7 @@ func (p *Peer) EthProcess(ethPkt []byte, conn WriteToer, ready chan struct{}) {
        now := time.Now()
        size := len(ethPkt)
        // If this heartbeat is necessary
-       if size == 0 && !p.LastSent.Add(heartbeatPeriodGet()).Before(now) {
+       if size == 0 && !p.LastSent.Add(p.Timeout).Before(now) {
                return
        }
        copy(p.buf, Emptiness)
@@ -309,7 +344,7 @@ func (p *Peer) EthProcess(ethPkt []byte, conn WriteToer, ready chan struct{}) {
        salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key)
        copy(p.buf[S20BS-NonceSize:S20BS], p.nonce)
        copy(p.keyAuth[:], p.buf[:KeySize])
-       if NoiseEnable {
+       if p.NoiseEnable {
                p.frame = p.buf[S20BS-NonceSize : S20BS+MTU-NonceSize-poly1305.TagSize]
        } else {
                p.frame = p.buf[S20BS-NonceSize : S20BS+PktSizeSize+size]
@@ -319,8 +354,8 @@ func (p *Peer) EthProcess(ethPkt []byte, conn WriteToer, ready chan struct{}) {
        p.BytesOut += int64(len(p.frame) + poly1305.TagSize)
        p.FramesOut++
 
-       if cprEnable {
-               p.willSentCycle = p.LastSent.Add(cprCycle)
+       if p.CPRCycle != time.Duration(0) {
+               p.willSentCycle = p.LastSent.Add(p.CPRCycle)
                if p.willSentCycle.After(now) {
                        time.Sleep(p.willSentCycle.Sub(now))
                        now = p.willSentCycle
index 11fe2c938eb14f7d8515b4596055e692d88396ec..2a56750dfce6a87a91856e8615fb0270bd003c2e 100644 (file)
@@ -17,7 +17,6 @@ var (
 
 func init() {
        MTU = 1500
-       Noncediff = 100
        addr, _ = net.ResolveUDPAddr("udp", "[::1]:1")
        peerId = IDDecode("ffffffffffffffffffffffffffffffff")
        peer = newPeer(addr, *peerId, 128, new([KeySize]byte))