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() {
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")
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)
}
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 {
}
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
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)
"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.
+++ /dev/null
-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
-}
@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
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.
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,
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
@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
@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
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.
* Timeout::
* Nonce difference::
* MTU::
-* Client part::
-* Server part::
* Stats::
* Noise::
* CPR::
+* Client part::
+* Server part::
* Example usage::
@end menu
@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
"log"
"os"
"path"
+ "strconv"
"strings"
"sync"
"time"
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.
}()
}
-// 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
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.
}
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
}
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]
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 {
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),
}
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
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)
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]
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
func init() {
MTU = 1500
- Noncediff = 100
addr, _ = net.ResolveUDPAddr("udp", "[::1]:1")
peerId = IDDecode("ffffffffffffffffffffffffffffffff")
peer = newPeer(addr, *peerId, 128, new([KeySize]byte))