From: Sergey Matveev Date: Sat, 2 May 2015 11:51:53 +0000 (+0300) Subject: Per-peer timeout, noncediff, noise, cpr settings X-Git-Tag: 3.0^2~1 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=1f0e56f6a94ab99ef3b46eb1b07f65c802f508c0;p=govpn.git Per-peer timeout, noncediff, noise, cpr settings Signed-off-by: Sergey Matveev --- diff --git a/cmd/govpn-client/main.go b/cmd/govpn-client/main.go index 9d99f08..30ba8e9 100644 --- a/cmd/govpn-client/main.go +++ b/cmd/govpn-client/main.go @@ -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) } diff --git a/cmd/govpn-server/main.go b/cmd/govpn-server/main.go index ffb6d23..6697f2b 100644 --- a/cmd/govpn-server/main.go +++ b/cmd/govpn-server/main.go @@ -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) diff --git a/common.go b/common.go index e5ff496..b87133a 100644 --- 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 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 -} diff --git a/doc/client.texi b/doc/client.texi index 52f6429..3cb8927 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -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 diff --git a/doc/cpr.texi b/doc/cpr.texi index 70572bf..88339db 100644 --- a/doc/cpr.texi +++ b/doc/cpr.texi @@ -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. diff --git a/doc/overview.texi b/doc/overview.texi index 4616af7..1886f03 100644 --- a/doc/overview.texi +++ b/doc/overview.texi @@ -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 diff --git a/doc/server.texi b/doc/server.texi index 69fee73..4fd074c 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -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. diff --git a/doc/user.texi b/doc/user.texi index 075f299..a77fd8a 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -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 diff --git a/identify.go b/identify.go index 6c16da0..d6a324f 100644 --- a/identify.go +++ b/identify.go @@ -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. diff --git a/transport.go b/transport.go index 6065c5e..f13c503 100644 --- a/transport.go +++ b/transport.go @@ -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 diff --git a/transport_test.go b/transport_test.go index 11fe2c9..2a56750 100644 --- a/transport_test.go +++ b/transport_test.go @@ -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))