@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. Two more bytes in TCP mode.
+26 bytes per packet.
@item Handshake overhead
4 UDP (2 from client, 2 from server) packets (round-trips for TCP),
-264 bytes total payload (8 bytes more in TCP mode).
+264 bytes total payload.
@item Entropy required
832 bits in average on client, 832 bits in average on server side per
handshake.
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.
+TCP is more resource hungry. Moreover because of packet loss and TCP
+reliability it can lead to "meltdown" effect: significant performance
+loss of underlying TCP connections. Generally TCP is not advisable for
+VPNs, but it can help with some nasty firewalls.
@item Ability to tunnel only specified TCP connections, without full
featured VPN solution. Similar to ssh -R.
@item Ability to noise handshake packets sizes.
-@item Increase performance.
@item Randomize ports usage.
@end itemize
@section Transport protocol
@verbatim
-[PktLen] + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) + ENCn(SERIAL) +
- AUTH(ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) + ENCn(SERIAL))
+TAG || ENCRYPTED || NONCE <-- PACKET
+ ^ ^ ^
+ | | |
+ | | +------------+
+ | | |
+ | +------------+ |
+ | | |
+ +-->AUTH(AUTH_KEY, ENCRYPTED || NONCE)
+ ^ ^
+ | |
++-----------------------+ |
+| |
+| +--------------+
+| |
++--> ENCRYPT(KEY, NONCE, PAYLOAD)
+ ^ ^
+ | |
+ | +--> SIZE || DATA [|| NOISE]
+ |
+ +--> PRP(PRP_KEY, SERIAL)
@end verbatim
-All transport and handshake messages are indistinguishable from
-pseudo random noise, except when using TCP connections.
-
-@code{PktLen} is used only with TCP connections. It is big-endian
-@emph{uint16} length of the whole packet (except @code{PktLen} itself).
-
@code{SERIAL} is message's serial number. Odds are reserved for
client(->server) messages, evens for server(->client) messages.
-@code{ENCn} is XTEA block cipher algorithm used here as PRP (pseudo
+@code{PRP} is XTEA block cipher algorithm used here as PRP (pseudo
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.
+before transmission.
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.
+@verbatim
+PRP_KEY = ENCRYPT(KEY, 0, 128-bit)
+@end verbatim
+
+@code{ENCRYPT} is Salsa20 stream cipher, with established session
+@code{KEY} and obfuscated @code{SERIAL} used as a nonce. 512 bit of
+Salsa20's output is ignored and only remaining is XORed with ther data,
+encrypting it.
-@code{DATA_SIZE} is big-endian @emph{uint16} storing length of the
+@code{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}.
+Salsa20's output are used as a one-time key for @code{AUTH}.
+
+@verbatim
+AUTH_KEY = ENCRYPT(KEY, NONCE, 256 bit)
+@end verbatim
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
-and check if received one is greater, but because of UDP packets
-reordering this can lead to valid packets dropping and overall
-performance degradation. We store up to 256 seen nonces in hash
-structure, in two swapping buckets.
+drop when receiving duplicate ones.
What network performance can user expect? For example single
@emph{Intel i5-2450M 2.5 GHz} core on @emph{FreeBSD 10.2 amd64}
-with @emph{Go 1.5} gives 435 Mbps TCP (over UDP) throughput.
+with @emph{Go 1.5.1} gives 786 Mbps (UDP transport) throughput.
@menu
* EGD:: Entropy gathering daemon
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Simple secure free software virtual private network daemon client.
+// Simple secure, DPI/censorship-resistant free software VPN daemon client.
package main
import (
noisy = flag.Bool("noise", false, "Enable noise appending")
cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
egdPath = flag.String("egd", "", "Optional path to EGD socket")
+
+ conf *govpn.PeerConf
+ tap *govpn.TAP
+ timeout int
+ firstUpCall bool = true
+ knownPeers govpn.KnownPeers
)
func main() {
flag.Parse()
- timeout := *timeoutP
+ timeout = *timeoutP
var err error
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
}
pub, priv := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath))
- conf := &govpn.PeerConf{
+ conf = &govpn.PeerConf{
Id: id,
Timeout: time.Second * time.Duration(timeout),
NoiseEnable: *noisy,
DSAPriv: priv,
}
govpn.PeersInitDummy(id, conf)
+ log.Println(govpn.VersionGet())
- var conn govpn.RemoteConn
- var sink chan []byte
- var ready chan struct{}
- switch *proto {
- case "udp":
- conn, sink, ready = startUDP()
- case "tcp":
- if *proxyAddr != "" {
- conn, sink, ready = proxyTCP()
- } else {
- conn, sink, ready = startTCP()
- }
- default:
- log.Fatalln("Unknown protocol specified")
- }
-
- tap, ethSink, ethReady, _, err := govpn.TAPListen(
- *ifaceName,
- time.Second*time.Duration(timeout),
- *cpr,
- )
+ tap, err = govpn.TAPListen(*ifaceName)
if err != nil {
log.Fatalln("Can not listen on TAP interface:", err)
}
- timeouts := 0
- firstUpCall := true
- var peer *govpn.Peer
- var ethPkt []byte
- var pkt []byte
- 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)
termSignal := make(chan os.Signal, 1)
signal.Notify(termSignal, os.Interrupt, os.Kill)
- log.Println("Starting handshake")
- handshake := govpn.HandshakeStart(*remoteAddr, conn, conf)
-
MainCycle:
for {
- if peer != nil && (peer.BytesIn+peer.BytesOut) > govpn.MaxBytesPerKey {
- peer.Zero()
- peer = nil
- handshake = govpn.HandshakeStart(*remoteAddr, conn, conf)
- log.Println("Rehandshaking")
+ timeouted := make(chan struct{})
+ rehandshaking := make(chan struct{})
+ termination := make(chan struct{})
+ switch *proto {
+ case "udp":
+ go startUDP(timeouted, rehandshaking, termination)
+ case "tcp":
+ if *proxyAddr != "" {
+ go proxyTCP(timeouted, rehandshaking, termination)
+ } else {
+ go startTCP(timeouted, rehandshaking, termination)
+ }
+ default:
+ log.Fatalln("Unknown protocol specified")
}
select {
case <-termSignal:
+ log.Fatalln("Finishing...")
+ termination <- struct{}{}
break MainCycle
- case ethPkt = <-ethSink:
- if peer == nil {
- if len(ethPkt) > 0 {
- ethReady <- struct{}{}
- }
- continue
- }
- peer.EthProcess(ethPkt, ethReady)
- case pkt = <-sink:
- timeouts++
- if timeouts >= timeout {
- break MainCycle
- }
- if pkt == nil {
- ready <- struct{}{}
- continue
- }
-
- if peer == nil {
- if govpn.IDsCache.Find(pkt) == nil {
- log.Println("Invalid identity in handshake packet")
- ready <- struct{}{}
- continue
- }
- if p := handshake.Client(pkt); p != nil {
- log.Println("Handshake completed")
- if firstUpCall {
- go govpn.ScriptCall(*upPath, *ifaceName)
- firstUpCall = false
- }
- peer = p
- handshake.Zero()
- handshake = nil
- }
- ready <- struct{}{}
- continue
- }
- if peer == nil {
- ready <- struct{}{}
- continue
- }
- if peer.PktProcess(pkt, tap, ready) {
- timeouts = 0
- }
+ case <-timeouted:
+ break MainCycle
+ case <-rehandshaking:
}
+ close(timeouted)
+ close(rehandshaking)
+ close(termination)
}
govpn.ScriptCall(*downPath, *ifaceName)
}
"log"
"net"
"net/http"
-
- "govpn"
)
-func proxyTCP() (govpn.RemoteConn, chan []byte, chan struct{}) {
+func proxyTCP(timeouted, rehandshaking, termination chan struct{}) {
proxyAddr, err := net.ResolveTCPAddr("tcp", *proxyAddr)
if err != nil {
log.Fatalln("Can not resolve proxy address:", err)
if err != nil || resp.StatusCode != http.StatusOK {
log.Fatalln("Unexpected response from proxy")
}
- sink := make(chan []byte)
- ready := make(chan struct{})
- go handleTCP(conn, sink, ready)
- go func() { ready <- struct{}{} }()
- return TCPSender{conn}, sink, ready
+ go handleTCP(conn, timeouted, rehandshaking, termination)
}
package main
import (
+ "bytes"
"encoding/binary"
"log"
"net"
+ "sync/atomic"
+ "time"
"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 (c TCPSender) Reorderable() bool {
- return false
-}
-
-func startTCP() (govpn.RemoteConn, chan []byte, chan struct{}) {
+func startTCP(timeouted, rehandshaking, termination 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}
+ conn, err := net.DialTCP("tcp", nil, remote)
if err != nil {
- log.Fatalln("Can not connect TCP:", err)
+ log.Fatalln("Can not connect to remote address:", err)
}
- sink := make(chan []byte)
- ready := make(chan struct{})
- go handleTCP(c, sink, ready)
- go func() { ready <- struct{}{} }()
- return conn, sink, ready
+ handleTCP(conn, timeouted, rehandshaking, termination)
}
-func handleTCP(conn *net.TCPConn, sink chan []byte, ready chan struct{}) {
- var err error
- var n int
- var sizeNbuf int
- sizeBuf := make([]byte, 2)
- var sizeNeed uint16
- var bufN uint16
+func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan struct{}) {
+ hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
buf := make([]byte, govpn.MTU)
+ var n int
+ var err error
+ var prev int
+ var peer *govpn.Peer
+ var terminator chan struct{}
+HandshakeCycle:
for {
- <-ready
- if sizeNbuf != 2 {
- n, err = conn.Read(sizeBuf[sizeNbuf:2])
- if err != nil {
- break
- }
- sizeNbuf += n
- if sizeNbuf != 2 {
- sink <- nil
- continue
- }
- sizeNeed = binary.BigEndian.Uint16(sizeBuf)
- if int(sizeNeed) > govpn.MTU-2 {
- log.Println("Invalid TCP size, skipping")
- sizeNbuf = 0
- sink <- nil
- continue
- }
- bufN = 0
+ select {
+ case <-termination:
+ break HandshakeCycle
+ default:
+ }
+ if prev == govpn.MTU {
+ break HandshakeCycle
+ }
+
+ conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ // Either EOFed or timeouted
+ break HandshakeCycle
}
- ReadMore:
- if sizeNeed != bufN {
- n, err = conn.Read(buf[bufN:sizeNeed])
- if err != nil {
- break
+
+ prev += n
+ peerId := govpn.IDsCache.Find(buf[:prev])
+ if peerId == nil {
+ continue
+ }
+ peer = hs.Client(buf[:prev])
+ prev = 0
+ if peer == nil {
+ continue
+ }
+ log.Println("Handshake completed")
+ knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
+ if firstUpCall {
+ go govpn.ScriptCall(*upPath, *ifaceName)
+ firstUpCall = false
+ }
+ hs.Zero()
+ terminator = make(chan struct{})
+ go func() {
+ heartbeat := time.NewTicker(peer.Timeout)
+ var data []byte
+ Processor:
+ for {
+ select {
+ case <-heartbeat.C:
+ peer.EthProcess(nil)
+ case <-terminator:
+ break Processor
+ case data = <-tap.Sink:
+ peer.EthProcess(data)
+ }
}
- bufN += uint16(n)
- goto ReadMore
+ heartbeat.Stop()
+ peer.Zero()
+ }()
+ break HandshakeCycle
+ }
+ if hs != nil {
+ hs.Zero()
+ }
+ if peer == nil {
+ return
+ }
+
+ nonceExpectation := make([]byte, govpn.NonceSize)
+ binary.BigEndian.PutUint64(nonceExpectation, peer.NonceExpect)
+ peer.NonceCipher.Encrypt(nonceExpectation, nonceExpectation)
+ prev = 0
+ var i int
+TransportCycle:
+ for {
+ select {
+ case <-termination:
+ break TransportCycle
+ default:
+ }
+ if prev == govpn.MTU {
+ timeouted <- struct{}{}
+ break TransportCycle
+ }
+ conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ // Either EOFed or timeouted
+ timeouted <- struct{}{}
+ break TransportCycle
+ }
+ prev += n
+ CheckMore:
+ i = bytes.Index(buf[:prev], nonceExpectation)
+ if i == -1 {
+ continue
}
- sizeNbuf = 0
- sink <- buf[:sizeNeed]
+ if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
+ timeouted <- struct{}{}
+ break TransportCycle
+ }
+ if atomic.LoadInt64(&peer.BytesIn)+atomic.LoadInt64(&peer.BytesOut) > govpn.MaxBytesPerKey {
+ log.Println("Need rehandshake")
+ rehandshaking <- struct{}{}
+ break TransportCycle
+ }
+ binary.BigEndian.PutUint64(nonceExpectation, peer.NonceExpect)
+ peer.NonceCipher.Encrypt(nonceExpectation, nonceExpectation)
+ copy(buf, buf[i+govpn.NonceSize:prev])
+ prev = prev - i - govpn.NonceSize
+ goto CheckMore
+ }
+ if terminator != nil {
+ terminator <- struct{}{}
}
+ peer.Zero()
}
import (
"log"
"net"
+ "sync/atomic"
"time"
"govpn"
)
-type UDPSender struct {
- conn *net.UDPConn
-}
-
-func (c UDPSender) Write(data []byte) (int, error) {
- return c.conn.Write(data)
-}
-
-func (c UDPSender) Reorderable() bool {
- return true
-}
-
-func startUDP() (govpn.RemoteConn, chan []byte, chan struct{}) {
+func startUDP(timeouted, rehandshaking, termination 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, err := net.DialUDP("udp", nil, remote)
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
+
+ hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
+ buf := make([]byte, govpn.MTU)
+ var n int
+ var timeouts int
+ var peer *govpn.Peer
+ var terminator chan struct{}
+MainCycle:
+ for {
+ select {
+ case <-termination:
+ break MainCycle
+ default:
+ }
+
+ conn.SetReadDeadline(time.Now().Add(time.Second))
+ n, err = conn.Read(buf)
+ if timeouts == timeout {
+ log.Println("Timeouted")
+ timeouted <- struct{}{}
+ break
+ }
+ if err != nil {
+ timeouts++
+ continue
+ }
+ if peer != nil {
+ if peer.PktProcess(buf[:n], tap, true) {
+ timeouts = 0
+ } else {
+ timeouts++
+ }
+ if atomic.LoadInt64(&peer.BytesIn)+atomic.LoadInt64(&peer.BytesOut) > govpn.MaxBytesPerKey {
+ log.Println("Need rehandshake")
+ terminator <- struct{}{}
+ terminator = nil
+ rehandshaking <- struct{}{}
+ break MainCycle
}
- sink <- buf[:n]
+ continue
+ }
+ if govpn.IDsCache.Find(buf[:n]) == nil {
+ log.Println("Invalid identity in handshake packet")
+ continue
+ }
+ timeouts = 0
+ peer = hs.Client(buf[:n])
+ if peer == nil {
+ continue
+ }
+ log.Println("Handshake completed")
+ knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
+ if firstUpCall {
+ go govpn.ScriptCall(*upPath, *ifaceName)
+ firstUpCall = false
}
- }()
- ready <- struct{}{}
- return UDPSender{c}, sink, ready
+ hs.Zero()
+ terminator = make(chan struct{})
+ go func() {
+ heartbeat := time.NewTicker(peer.Timeout)
+ var data []byte
+ Processor:
+ for {
+ select {
+ case <-heartbeat.C:
+ peer.EthProcess(nil)
+ case <-terminator:
+ break Processor
+ case data = <-tap.Sink:
+ peer.EthProcess(data)
+ }
+ }
+ heartbeat.Stop()
+ peer.Zero()
+ }()
+ }
+ if terminator != nil {
+ terminator <- struct{}{}
+ }
+ if hs != nil {
+ hs.Zero()
+ }
}
--- /dev/null
+package main
+
+import (
+ "bytes"
+ "path"
+ "sync"
+ "time"
+
+ "govpn"
+)
+
+type PeerState struct {
+ peer *govpn.Peer
+ terminator chan struct{}
+ tap *govpn.TAP
+}
+
+var (
+ handshakes map[string]*govpn.Handshake = make(map[string]*govpn.Handshake)
+ hsLock sync.RWMutex
+
+ peers map[string]*PeerState = make(map[string]*PeerState)
+ peersLock sync.RWMutex
+
+ peersById map[govpn.PeerId]string = make(map[govpn.PeerId]string)
+ peersByIdLock sync.RWMutex
+
+ knownPeers govpn.KnownPeers
+ kpLock sync.RWMutex
+)
+
+func peerReady(ps PeerState) {
+ var data []byte
+ heartbeat := time.NewTicker(ps.peer.Timeout)
+Processor:
+ for {
+ select {
+ case <-heartbeat.C:
+ ps.peer.EthProcess(nil)
+ case <-ps.terminator:
+ break Processor
+ case data = <-ps.tap.Sink:
+ ps.peer.EthProcess(data)
+ }
+ }
+ ps.peer.Zero()
+ heartbeat.Stop()
+}
+
+func callUp(peerId *govpn.PeerId) (string, error) {
+ upPath := path.Join(govpn.PeersPath, peerId.String(), "up.sh")
+ result, err := govpn.ScriptCall(upPath, "")
+ if err != nil {
+ return "", err
+ }
+ sepIndex := bytes.Index(result, []byte{'\n'})
+ if sepIndex < 0 {
+ sepIndex = len(result)
+ }
+ ifaceName := string(result[:sepIndex])
+ return ifaceName, nil
+}
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Simple secure free software virtual private network daemon.
+// Simple secure, DPI/censorship-resistant free software VPN daemon.
package main
import (
- "bytes"
"flag"
"log"
"net"
egdPath = flag.String("egd", "", "Optional path to EGD socket")
)
-type Pkt struct {
- addr string
- conn govpn.RemoteConn
- data []byte
- ready chan struct{}
-}
-
-type PeerReadyEvent struct {
- peer *govpn.Peer
- iface string
-}
-
-type PeerState struct {
- peer *govpn.Peer
- tap *govpn.TAP
- sink chan []byte
- ready chan struct{}
- terminate chan struct{}
-}
-
-func NewPeerState(peer *govpn.Peer, iface string) *PeerState {
- tap, sink, ready, terminate, err := govpn.TAPListen(iface, peer.Timeout, peer.CPR)
- if err != nil {
- log.Println("Unable to create Eth", err)
- return nil
- }
- state := PeerState{
- peer: peer,
- tap: tap,
- sink: sink,
- ready: ready,
- terminate: terminate,
- }
- return &state
-}
-
-type EthEvent struct {
- peer *govpn.Peer
- data []byte
- ready chan struct{}
-}
-
func main() {
flag.Parse()
timeout := time.Second * time.Duration(govpn.TimeoutDefault)
govpn.MTU = *mtu
govpn.PeersInit(*peersPath)
+ knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer))
if *egdPath != "" {
log.Println("Using", *egdPath, "EGD")
govpn.EGDInit(*egdPath)
}
- sink := make(chan Pkt)
switch *proto {
case "udp":
- startUDP(sink)
+ startUDP()
case "tcp":
- startTCP(sink)
+ startTCP()
case "all":
- startUDP(sink)
- startTCP(sink)
+ startUDP()
+ startTCP()
default:
log.Fatalln("Unknown protocol specified")
}
hsHeartbeat := time.Tick(timeout)
go func() { <-hsHeartbeat }()
- var state *govpn.Handshake
- var peerState *PeerState
- var peer *govpn.Peer
- var exists bool
- states := make(map[string]*govpn.Handshake)
- peers := make(map[string]*PeerState)
- peerReadySink := make(chan PeerReadyEvent)
- knownPeers := govpn.KnownPeers(make(map[string]**govpn.Peer))
- var peerReady PeerReadyEvent
- var pkt Pkt
- var ethEvent EthEvent
- var peerId *govpn.PeerId
- var peerConf *govpn.PeerConf
- var handshakeProcessForce bool
- ethSink := make(chan EthEvent)
-
log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU())
if *stats != "" {
log.Println("Stats are going to listen on", *stats)
go govpn.StatsProcessor(statsPort, &knownPeers)
}
if *proxy != "" {
- go proxyStart(sink)
+ go proxyStart()
}
log.Println("Server started")
+ var needsDeletion bool
MainCycle:
for {
select {
break MainCycle
case <-hsHeartbeat:
now := time.Now()
- for addr, hs := range states {
+ hsLock.Lock()
+ for addr, hs := range handshakes {
if hs.LastPing.Add(timeout).Before(now) {
log.Println("Deleting handshake state", addr)
hs.Zero()
- delete(states, addr)
+ delete(handshakes, addr)
}
}
- for addr, state := range peers {
- if state.peer.LastPing.Add(timeout).Before(now) {
- log.Println("Deleting peer", state.peer)
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ for addr, ps := range peers {
+ ps.peer.BusyR.Lock()
+ needsDeletion = ps.peer.LastPing.Add(timeout).Before(now)
+ ps.peer.BusyR.Unlock()
+ if needsDeletion {
+ log.Println("Deleting peer", ps.peer)
delete(peers, addr)
delete(knownPeers, addr)
+ delete(peersById, *ps.peer.Id)
downPath := path.Join(
govpn.PeersPath,
- state.peer.Id.String(),
+ ps.peer.Id.String(),
"down.sh",
)
- go govpn.ScriptCall(downPath, state.tap.Name)
- state.terminate <- struct{}{}
- state.peer.Zero()
+ go govpn.ScriptCall(downPath, ps.tap.Name)
+ ps.terminator <- struct{}{}
}
}
- case peerReady = <-peerReadySink:
- for addr, state := range peers {
- if state.tap.Name != peerReady.iface {
- continue
- }
- delete(peers, addr)
- delete(knownPeers, addr)
- state.terminate <- struct{}{}
- state.peer.Zero()
- break
- }
- state := NewPeerState(peerReady.peer, peerReady.iface)
- if state == nil {
- continue
- }
- peers[peerReady.peer.Addr] = state
- knownPeers[peerReady.peer.Addr] = &peerReady.peer
- states[peerReady.peer.Addr].Zero()
- delete(states, peerReady.peer.Addr)
- log.Println("Registered interface", peerReady.iface, "with peer", peer)
- go func(state *PeerState) {
- for data := range state.sink {
- ethSink <- EthEvent{
- peer: state.peer,
- data: data,
- ready: state.ready,
- }
- }
- }(state)
- case ethEvent = <-ethSink:
- if s, exists := peers[ethEvent.peer.Addr]; !exists || s.peer != ethEvent.peer {
- continue
- }
- ethEvent.peer.EthProcess(ethEvent.data, ethEvent.ready)
- case pkt = <-sink:
- if pkt.data == nil {
- pkt.ready <- struct{}{}
- continue
- }
- handshakeProcessForce = false
- HandshakeProcess:
- if _, exists = peers[pkt.addr]; handshakeProcessForce || !exists {
- peerId = govpn.IDsCache.Find(pkt.data)
- if peerId == nil {
- log.Println("Unknown identity from", pkt.addr)
- pkt.ready <- struct{}{}
- continue
- }
- peerConf = peerId.Conf()
- if peerConf == nil {
- log.Println("Can not get peer configuration", peerId.String())
- pkt.ready <- struct{}{}
- continue
- }
- state, exists = states[pkt.addr]
- if !exists {
- state = govpn.HandshakeNew(pkt.addr, pkt.conn, peerConf)
- states[pkt.addr] = state
- }
- peer = state.Server(pkt.data)
- if peer != nil {
- log.Println("Peer handshake finished", peer)
- if _, exists = peers[pkt.addr]; exists {
- go func() {
- peerReadySink <- PeerReadyEvent{
- peer, peers[pkt.addr].tap.Name,
- }
- }()
- } else {
- go func() {
- upPath := path.Join(govpn.PeersPath, peer.Id.String(), "up.sh")
- result, err := govpn.ScriptCall(upPath, "")
- if err != nil {
- return
- }
- sepIndex := bytes.Index(result, []byte{'\n'})
- if sepIndex < 0 {
- sepIndex = len(result)
- }
- ifaceName := string(result[:sepIndex])
- peerReadySink <- PeerReadyEvent{peer, ifaceName}
- }()
- }
- }
- if !handshakeProcessForce {
- pkt.ready <- struct{}{}
- }
- continue
- }
- peerState, exists = peers[pkt.addr]
- if !exists {
- 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, pkt.ready) {
- handshakeProcessForce = true
- goto HandshakeProcess
- }
+ hsLock.Unlock()
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
}
}
}
"net/http"
)
-type proxyHandler struct {
- sink chan Pkt
-}
+type proxyHandler struct{}
func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
conn, _, err := w.(http.Hijacker).Hijack()
return
}
conn.Write([]byte("HTTP/1.0 200 OK\n\n"))
- ready := make(chan struct{}, 1)
- go handleTCP(conn, p.sink, ready)
- ready <- struct{}{}
-
+ go handleTCP(conn)
}
-func proxyStart(sink chan Pkt) {
+func proxyStart() {
log.Println("HTTP proxy listening on:", *proxy)
s := &http.Server{
Addr: *proxy,
- Handler: proxyHandler{sink},
+ Handler: proxyHandler{},
}
log.Println("HTTP proxy result:", s.ListenAndServe())
}
package main
import (
+ "bytes"
"encoding/binary"
"log"
"net"
+ "time"
"govpn"
)
-type TCPSender struct {
- conn net.Conn
-}
-
-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 (c TCPSender) Reorderable() bool {
- return false
-}
-
-func startTCP(sink chan Pkt) {
+func startTCP() {
bind, err := net.ResolveTCPAddr("tcp", *bindAddr)
if err != nil {
log.Fatalln("Can not resolve bind address:", err)
log.Println("Listening on TCP", *bindAddr)
go func() {
for {
- conn, _ := listener.AcceptTCP()
- ready := make(chan struct{}, 1)
- go handleTCP(conn, sink, ready)
- ready <- struct{}{}
+ conn, err := listener.AcceptTCP()
+ if err != nil {
+ log.Println("Error accepting TCP:", err)
+ continue
+ }
+ go handleTCP(conn)
}
}()
}
-func handleTCP(conn net.Conn, sink chan Pkt, ready chan struct{}) {
+func handleTCP(conn net.Conn) {
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)
+ var n int
+ var err error
+ var prev int
+ var hs *govpn.Handshake
+ var ps *PeerState
+ var peer *govpn.Peer
+ var tap *govpn.TAP
+ var conf *govpn.PeerConf
for {
- <-ready
- if sizeNbuf != 2 {
- n, err = conn.Read(sizeBuf[sizeNbuf:2])
- if err != nil {
+ if prev == govpn.MTU {
+ break
+ }
+ conn.SetReadDeadline(time.Now().Add(time.Duration(govpn.TimeoutDefault) * time.Second))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ // Either EOFed or timeouted
+ break
+ }
+ prev += n
+ peerId := govpn.IDsCache.Find(buf[:prev])
+ if peerId == nil {
+ continue
+ }
+ if hs == nil {
+ conf = peerId.Conf()
+ if conf == nil {
+ log.Println("Can not get peer configuration:", peerId.String())
break
}
- sizeNbuf += n
- if sizeNbuf != 2 {
- sink <- Pkt{ready: ready}
- continue
+ hs = govpn.NewHandshake(addr, conn, conf)
+ }
+ peer = hs.Server(buf[:prev])
+ prev = 0
+ if peer == nil {
+ continue
+ }
+ hs.Zero()
+ peersByIdLock.RLock()
+ addrPrev, exists := peersById[*peer.Id]
+ peersByIdLock.RUnlock()
+ if exists {
+ peersLock.RLock()
+ tap = peers[addrPrev].tap
+ ps = &PeerState{
+ peer: peer,
+ tap: tap,
+ terminator: peers[addrPrev].terminator,
}
- sizeNeed = binary.BigEndian.Uint16(sizeBuf)
- if int(sizeNeed) > govpn.MTU-2 {
- log.Println("Invalid TCP size, skipping")
- sizeNbuf = 0
- sink <- Pkt{ready: ready}
- continue
+ peersLock.RUnlock()
+ ps.terminator <- struct{}{}
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ delete(peers, addrPrev)
+ delete(knownPeers, addrPrev)
+ delete(peersById, *peer.Id)
+ peers[addr] = ps
+ knownPeers[addr] = &peer
+ peersById[*peer.Id] = addr
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ go peerReady(*ps)
+ log.Println("Rehandshake finished:", peer.Id.String())
+ } else {
+ ifaceName, err := callUp(peer.Id)
+ if err != nil {
+ break
}
- bufN = 0
- }
- ReadMore:
- if sizeNeed != bufN {
- n, err = conn.Read(buf[bufN:sizeNeed])
+ tap, err = govpn.TAPListen(ifaceName)
if err != nil {
+ log.Println("Unable to create TAP:", err)
break
}
- bufN += uint16(n)
- goto ReadMore
+ ps = &PeerState{
+ peer: peer,
+ tap: tap,
+ terminator: make(chan struct{}, 1),
+ }
+ go peerReady(*ps)
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ peers[addr] = ps
+ peersById[*peer.Id] = addr
+ knownPeers[addr] = &peer
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ log.Println("New peer:", peer.Id.String())
+ }
+ break
+ }
+ if hs != nil {
+ hs.Zero()
+ }
+ if peer == nil {
+ return
+ }
+
+ nonceExpectation := make([]byte, govpn.NonceSize)
+ binary.BigEndian.PutUint64(nonceExpectation, peer.NonceExpect)
+ peer.NonceCipher.Encrypt(nonceExpectation, nonceExpectation)
+ prev = 0
+ var i int
+ for {
+ if prev == govpn.MTU {
+ break
+ }
+ conn.SetReadDeadline(time.Now().Add(conf.Timeout))
+ n, err = conn.Read(buf[prev:])
+ if err != nil {
+ // Either EOFed or timeouted
+ break
+ }
+ prev += n
+ CheckMore:
+ i = bytes.Index(buf[:prev], nonceExpectation)
+ if i == -1 {
+ continue
}
- sizeNbuf = 0
- sink <- Pkt{
- addr,
- TCPSender{conn},
- buf[:sizeNeed],
- ready,
+ if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
+ break
}
+ binary.BigEndian.PutUint64(nonceExpectation, peer.NonceExpect)
+ peer.NonceCipher.Encrypt(nonceExpectation, nonceExpectation)
+ copy(buf, buf[i+govpn.NonceSize:prev])
+ prev = prev - i - govpn.NonceSize
+ goto CheckMore
}
+ peer.Zero()
}
import (
"log"
"net"
- "time"
"govpn"
)
return c.conn.WriteToUDP(data, c.addr)
}
-func (c UDPSender) Reorderable() bool {
- return true
-}
+var (
+ // Buffers for UDP parallel processing
+ udpBufs chan []byte = make(chan []byte, 1<<8)
+)
-func startUDP(sink chan Pkt) {
+func startUDP() {
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)
+ conn, err := net.ListenUDP("udp", bind)
if err != nil {
log.Fatalln("Can not listen on UDP:", err)
}
log.Println("Listening on UDP", *bindAddr)
+ udpBufs <- make([]byte, govpn.MTU)
go func() {
- buf := make([]byte, govpn.MTU)
- var n int
+ var buf []byte
var raddr *net.UDPAddr
+ var addr string
+ var n int
var err error
+ var ps *PeerState
+ var hs *govpn.Handshake
+ var addrPrev string
+ var exists bool
+ var peerId *govpn.PeerId
+ var peer *govpn.Peer
+ var conf *govpn.PeerConf
for {
- <-ready
- lconn.SetReadDeadline(time.Now().Add(time.Second))
- n, raddr, err = lconn.ReadFromUDP(buf)
+ buf = <-udpBufs
+
+ n, raddr, err = conn.ReadFromUDP(buf)
if err != nil {
- sink <- Pkt{ready: ready}
- continue
+ break
+ }
+ addr = raddr.String()
+
+ peersLock.RLock()
+ ps, exists = peers[addr]
+ peersLock.RUnlock()
+ if !exists {
+ goto CheckHandshake
+ }
+ go func(ps *govpn.Peer, tap *govpn.TAP, buf []byte, n int) {
+ peer.PktProcess(buf[:n], tap, true)
+ udpBufs <- buf
+ }(ps.peer, ps.tap, buf, n)
+ continue
+ CheckHandshake:
+ hsLock.RLock()
+ hs, exists = handshakes[addr]
+ hsLock.RUnlock()
+ if !exists {
+ goto CheckID
+ }
+ peer = hs.Server(buf[:n])
+ if peer == nil {
+ goto Finished
+ }
+
+ log.Println("Peer handshake finished:", addr)
+ hs.Zero()
+ hsLock.Lock()
+ delete(handshakes, addr)
+ hsLock.Unlock()
+
+ go func() {
+ udpBufs <- make([]byte, govpn.MTU)
+ udpBufs <- make([]byte, govpn.MTU)
+ }()
+ peersByIdLock.RLock()
+ addrPrev, exists = peersById[*peer.Id]
+ peersByIdLock.RUnlock()
+ if exists {
+ peersLock.RLock()
+ ps = &PeerState{
+ peer: peer,
+ tap: peers[addrPrev].tap,
+ terminator: peers[addrPrev].terminator,
+ }
+ peersLock.RUnlock()
+ ps.terminator <- struct{}{}
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ delete(peers, addrPrev)
+ delete(knownPeers, addrPrev)
+ delete(peersById, *peer.Id)
+ peers[addr] = ps
+ knownPeers[addr] = &peer
+ peersById[*peer.Id] = addr
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ go func(ps PeerState) {
+ peerReady(ps)
+ <-udpBufs
+ <-udpBufs
+ }(*ps)
+ log.Println("Rehandshake finished:", peer.Id.String())
+ } else {
+ go func(addr string, peer *govpn.Peer) {
+ ifaceName, err := callUp(peer.Id)
+ if err != nil {
+ return
+ }
+ tap, err := govpn.TAPListen(ifaceName)
+ if err != nil {
+ log.Println("Unable to create TAP:", err)
+ return
+ }
+ ps = &PeerState{
+ peer: peer,
+ tap: tap,
+ terminator: make(chan struct{}, 1),
+ }
+ go func(ps PeerState) {
+ peerReady(ps)
+ <-udpBufs
+ <-udpBufs
+ }(*ps)
+ peersLock.Lock()
+ peersByIdLock.Lock()
+ kpLock.Lock()
+ peers[addr] = ps
+ knownPeers[addr] = &peer
+ peersById[*peer.Id] = addr
+ peersLock.Unlock()
+ peersByIdLock.Unlock()
+ kpLock.Unlock()
+ log.Println("New peer:", peer.Id.String())
+ }(addr, peer)
+ }
+ goto Finished
+ CheckID:
+ peerId = govpn.IDsCache.Find(buf[:n])
+ if peerId == nil {
+ log.Println("Unknown identity from:", addr)
+ goto Finished
}
- sink <- Pkt{
- raddr.String(),
- UDPSender{lconn, raddr},
- buf[:n],
- ready,
+ conf = peerId.Conf()
+ if conf == nil {
+ log.Println("Can not get peer configuration:", peerId.String())
+ goto Finished
}
+ hs = govpn.NewHandshake(
+ addr,
+ UDPSender{conn: conn, addr: raddr},
+ conf,
+ )
+ hs.Server(buf[:n])
+ hsLock.Lock()
+ handshakes[addr] = hs
+ hsLock.Unlock()
+ Finished:
+ udpBufs <- buf
}
}()
- ready <- struct{}{}
}
"os"
"os/exec"
"runtime"
+ "time"
)
const (
func VersionGet() string {
return "GoVPN version " + Version + " built with " + runtime.Version()
}
+
+func cprCycleCalculate(rate int) time.Duration {
+ if rate == 0 {
+ return time.Duration(0)
+ }
+ return time.Second / time.Duration(rate*(1<<10)/MTU)
+}
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-// Simple secure free software virtual private network daemon
+// Simple secure, DPI/censorship-resistant free software VPN daemon.
package govpn
"crypto/rand"
"crypto/subtle"
"encoding/binary"
+ "io"
"log"
"time"
type Handshake struct {
addr string
- conn RemoteConn
+ conn io.Writer
LastPing time.Time
Conf *PeerConf
dsaPubH *[ed25519.PublicKeySize]byte
}
// Create new handshake state.
-func HandshakeNew(addr string, conn RemoteConn, conf *PeerConf) *Handshake {
+func NewHandshake(addr string, conn io.Writer, conf *PeerConf) *Handshake {
state := Handshake{
addr: addr,
conn: conn,
// Start handshake's procedure from the client. It is the entry point
// for starting the handshake procedure. // First handshake packet
// will be sent immediately.
-func HandshakeStart(addr string, conn RemoteConn, conf *PeerConf) *Handshake {
- state := HandshakeNew(addr, conn, conf)
+func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake {
+ state := NewHandshake(addr, conn, conf)
var dhPubRepr *[32]byte
state.dhPriv, dhPubRepr = dhKeypairGen()
--- /dev/null
+package govpn
+
+import (
+ "encoding/binary"
+ "io"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "golang.org/x/crypto/poly1305"
+ "golang.org/x/crypto/salsa20"
+ "golang.org/x/crypto/xtea"
+)
+
+const (
+ NonceSize = 8
+ NonceBucketSize = 128
+ TagSize = poly1305.TagSize
+ // S20BS is Salsa20's internal blocksize in bytes
+ S20BS = 64
+ // Maximal amount of bytes transfered with single key (4 GiB)
+ MaxBytesPerKey int64 = 1 << 32
+ // Size of packet's size mark in bytes
+ PktSizeSize = 2
+ // Heartbeat rate, relative to Timeout
+ TimeoutHeartbeat = 4
+)
+
+func newNonceCipher(key *[32]byte) *xtea.Cipher {
+ nonceKey := make([]byte, 16)
+ salsa20.XORKeyStream(
+ nonceKey,
+ make([]byte, 32),
+ make([]byte, xtea.BlockSize),
+ key,
+ )
+ ciph, err := xtea.NewCipher(nonceKey)
+ if err != nil {
+ panic(err)
+ }
+ return ciph
+}
+
+type Peer struct {
+ Addr string
+ Id *PeerId
+ Conn io.Writer
+
+ // Traffic behaviour
+ NoiseEnable bool
+ CPR int
+ CPRCycle time.Duration `json:"-"`
+
+ // Cryptography related
+ Key *[SSize]byte `json:"-"`
+ NonceCipher *xtea.Cipher `json:"-"`
+ nonceRecv uint64
+ nonceLatest uint64
+ nonceOur uint64
+ NonceExpect uint64 `json:"-"`
+ nonceBucket0 map[uint64]struct{}
+ nonceBucket1 map[uint64]struct{}
+ nonceFound0 bool
+ nonceFound1 bool
+ nonceBucketN int32
+
+ // Timers
+ Timeout time.Duration `json:"-"`
+ Established time.Time
+ LastPing time.Time
+ LastSent time.Time
+ willSentCycle time.Time
+
+ // Statistics
+ BytesIn int64
+ BytesOut int64
+ BytesPayloadIn int64
+ BytesPayloadOut int64
+ FramesIn int
+ FramesOut int
+ FramesUnauth int
+ FramesDup int
+ HeartbeatRecv int
+ HeartbeatSent int
+
+ // Receiver
+ BusyR sync.Mutex
+ bufR []byte
+ tagR *[TagSize]byte
+ keyAuthR *[SSize]byte
+ pktSizeR uint16
+
+ // Transmitter
+ BusyT sync.Mutex
+ bufT []byte
+ tagT *[TagSize]byte
+ keyAuthT *[SSize]byte
+ frameT []byte
+ now time.Time
+}
+
+func (p *Peer) String() string {
+ return p.Id.String() + ":" + p.Addr
+}
+
+// Zero peer's memory state.
+func (p *Peer) Zero() {
+ p.BusyT.Lock()
+ p.BusyR.Lock()
+ sliceZero(p.Key[:])
+ sliceZero(p.bufR)
+ sliceZero(p.bufT)
+ sliceZero(p.keyAuthR[:])
+ sliceZero(p.keyAuthT[:])
+ p.BusyT.Unlock()
+ p.BusyR.Unlock()
+}
+
+func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[SSize]byte) *Peer {
+ now := time.Now()
+ 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,
+ Id: conf.Id,
+ Conn: conn,
+
+ NoiseEnable: noiseEnable,
+ CPR: conf.CPR,
+ CPRCycle: cprCycle,
+
+ Key: key,
+ NonceCipher: newNonceCipher(key),
+ nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
+ nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
+
+ Timeout: timeout,
+ Established: now,
+ LastPing: now,
+
+ bufR: make([]byte, S20BS+MTU+NonceSize),
+ bufT: make([]byte, S20BS+MTU+NonceSize),
+ tagR: new([TagSize]byte),
+ tagT: new([TagSize]byte),
+ keyAuthR: new([SSize]byte),
+ keyAuthT: new([SSize]byte),
+ }
+ if isClient {
+ peer.nonceOur = 1
+ peer.NonceExpect = 0 + 2
+ } else {
+ peer.nonceOur = 0
+ peer.NonceExpect = 1 + 2
+ }
+ return &peer
+
+}
+
+// Process incoming Ethernet packet.
+// ready channel is TAPListen's synchronization channel used to tell him
+// that he is free to receive new packets. Encrypted and authenticated
+// packets will be sent to remote Peer side immediately.
+func (p *Peer) EthProcess(data []byte) {
+ p.now = time.Now()
+ p.BusyT.Lock()
+
+ // Zero size is a heartbeat packet
+ if len(data) == 0 {
+ // If this heartbeat is necessary
+ if !p.LastSent.Add(p.Timeout).Before(p.now) {
+ p.BusyT.Unlock()
+ return
+ }
+ p.bufT[S20BS+0] = byte(0)
+ p.bufT[S20BS+1] = byte(0)
+ p.HeartbeatSent++
+ } else {
+ // Copy payload to our internal buffer and we are ready to
+ // accept the next one
+ binary.BigEndian.PutUint16(
+ p.bufT[S20BS:S20BS+PktSizeSize],
+ uint16(len(data)),
+ )
+ copy(p.bufT[S20BS+PktSizeSize:], data)
+ p.BytesPayloadOut += int64(len(data))
+ }
+
+ if p.NoiseEnable {
+ p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize]
+ } else {
+ p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize]
+ }
+ p.nonceOur += 2
+ binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
+ p.NonceCipher.Encrypt(
+ p.frameT[len(p.frameT)-NonceSize:],
+ p.frameT[len(p.frameT)-NonceSize:],
+ )
+ for i := 0; i < SSize; i++ {
+ p.bufT[i] = byte(0)
+ }
+ salsa20.XORKeyStream(
+ p.bufT[:S20BS+len(p.frameT)-NonceSize],
+ p.bufT[:S20BS+len(p.frameT)-NonceSize],
+ p.frameT[len(p.frameT)-NonceSize:],
+ p.Key,
+ )
+
+ copy(p.keyAuthT[:], p.bufT[:SSize])
+ poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
+
+ atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize))
+ p.FramesOut++
+
+ if p.CPRCycle != time.Duration(0) {
+ p.willSentCycle = p.LastSent.Add(p.CPRCycle)
+ if p.willSentCycle.After(p.now) {
+ time.Sleep(p.willSentCycle.Sub(p.now))
+ p.now = p.willSentCycle
+ }
+ }
+
+ p.LastSent = p.now
+ p.Conn.Write(append(p.tagT[:], p.frameT...))
+ p.BusyT.Unlock()
+}
+
+func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
+ p.BusyR.Lock()
+ for i := 0; i < SSize; i++ {
+ p.bufR[i] = byte(0)
+ }
+ copy(p.bufR[S20BS:], data[TagSize:])
+ salsa20.XORKeyStream(
+ p.bufR[:S20BS+len(data)-TagSize-NonceSize],
+ p.bufR[:S20BS+len(data)-TagSize-NonceSize],
+ data[len(data)-NonceSize:],
+ p.Key,
+ )
+
+ copy(p.keyAuthR[:], p.bufR[:SSize])
+ copy(p.tagR[:], data[:TagSize])
+ if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) {
+ p.FramesUnauth++
+ p.BusyR.Unlock()
+ return false
+ }
+
+ // Check if received nonce is known to us in either of two buckets.
+ // If yes, then this is ignored duplicate.
+ // Check from the oldest bucket, as in most cases this will result
+ // in constant time check.
+ // If Bucket0 is filled, then it becomes Bucket1.
+ p.NonceCipher.Decrypt(
+ data[len(data)-NonceSize:],
+ data[len(data)-NonceSize:],
+ )
+ p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
+ if reorderable {
+ _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
+ _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
+ if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
+ p.FramesDup++
+ p.BusyR.Unlock()
+ return false
+ }
+ p.nonceBucket0[p.nonceRecv] = struct{}{}
+ p.nonceBucketN++
+ if p.nonceBucketN == NonceBucketSize {
+ p.nonceBucket1 = p.nonceBucket0
+ p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
+ p.nonceBucketN = 0
+ }
+ } else {
+ if p.nonceRecv != p.NonceExpect {
+ p.FramesDup++
+ p.BusyR.Unlock()
+ return false
+ }
+ p.NonceExpect += 2
+ }
+ if p.nonceRecv > p.nonceLatest {
+ p.nonceLatest = p.nonceRecv
+ }
+
+ p.FramesIn++
+ atomic.AddInt64(&p.BytesIn, int64(len(data)))
+ p.LastPing = time.Now()
+ p.pktSizeR = binary.BigEndian.Uint16(p.bufR[S20BS : S20BS+PktSizeSize])
+
+ if p.pktSizeR == 0 {
+ p.HeartbeatRecv++
+ p.BusyR.Unlock()
+ return true
+ }
+ p.BytesPayloadIn += int64(p.pktSizeR)
+ tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR])
+ p.BusyR.Unlock()
+ return true
+}
)
type TAP struct {
- Name string
- dev io.ReadWriter
- buf []byte
- sink chan []byte
- ready chan struct{}
- synced bool
+ Name string
+ Sink chan []byte
+ dev io.ReadWriter
+ buf0 []byte
+ buf1 []byte
+ bufZ bool
}
+var (
+ taps = make(map[string]*TAP)
+)
+
// Return maximal acceptable TAP interface MTU. This is daemon's MTU
// minus nonce, MAC, packet size mark and Ethernet header sizes.
func TAPMaxMTU() int {
return nil, err
}
tap := TAP{
- Name: ifaceName,
- dev: tapRaw,
- buf: make([]byte, maxIfacePktSize),
- sink: make(chan []byte),
- ready: make(chan struct{}),
- synced: false,
+ Name: ifaceName,
+ dev: tapRaw,
+ buf0: make([]byte, maxIfacePktSize),
+ buf1: make([]byte, maxIfacePktSize),
+ Sink: make(chan []byte),
}
go func() {
var n int
var err error
+ var buf []byte
for {
- <-tap.ready
- n, err = tap.dev.Read(tap.buf)
+ if tap.bufZ {
+ buf = tap.buf0
+ } else {
+ buf = tap.buf1
+ }
+ tap.bufZ = !tap.bufZ
+ n, err = tap.dev.Read(buf)
if err != nil {
panic("Reading TAP:" + err.Error())
}
- tap.sink <- tap.buf[:n]
+ tap.Sink <- buf[:n]
}
}()
return &tap, nil
func (t *TAP) Write(data []byte) (n int, err error) {
return t.dev.Write(data)
}
+
+func TAPListen(ifaceName string) (*TAP, error) {
+ tap, exists := taps[ifaceName]
+ if exists {
+ return tap, nil
+ }
+ tap, err := NewTAP(ifaceName)
+ if err != nil {
+ return nil, err
+ }
+ taps[ifaceName] = tap
+ return tap, nil
+}
+++ /dev/null
-/*
-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 govpn
-
-import (
- "encoding/binary"
- "io"
- "time"
-
- "golang.org/x/crypto/poly1305"
- "golang.org/x/crypto/salsa20"
- "golang.org/x/crypto/xtea"
-)
-
-const (
- NonceSize = 8
- NonceBucketSize = 128
- // S20BS is Salsa20's internal blocksize in bytes
- S20BS = 64
- // Maximal amount of bytes transfered with single key (4 GiB)
- MaxBytesPerKey int64 = 1 << 32
- // Size of packet's size mark in bytes
- PktSizeSize = 2
- // Heartbeat rate, relative to Timeout
- TimeoutHeartbeat = 4
-)
-
-type RemoteConn interface {
- io.Writer
- // Can incoming packets be reordered
- Reorderable() bool
-}
-
-type Peer struct {
- Addr string
- Id *PeerId
- Conn RemoteConn
-
- // Traffic behaviour
- NoiseEnable bool
- CPR int
- CPRCycle time.Duration `json:"-"`
-
- // Cryptography related
- Key *[SSize]byte `json:"-"`
- NonceOur uint64 `json:"-"`
- NonceRecv uint64 `json:"-"`
- NonceCipher *xtea.Cipher `json:"-"`
- nonceExpect uint64 `json:"-"`
- nonceBucket0 map[uint64]struct{}
- nonceBucket1 map[uint64]struct{}
- nonceFound bool
- nonceBucketN int32
-
- // 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 *[32]byte
- nonceRecv uint64
- frame []byte
- nonce []byte
- pktSize uint16
- size int
- now time.Time
-
- // Statistics
- BytesIn int64
- BytesOut int64
- BytesPayloadIn int64
- BytesPayloadOut int64
- FramesIn int
- FramesOut int
- FramesUnauth int
- FramesDup int
- HeartbeatRecv int
- HeartbeatSent int
-}
-
-func (p *Peer) String() string {
- return p.Id.String() + ":" + p.Addr
-}
-
-// Zero peer's memory state.
-func (p *Peer) Zero() {
- sliceZero(p.Key[:])
- sliceZero(p.tag[:])
- sliceZero(p.keyAuth[:])
- sliceZero(p.buf)
- sliceZero(p.frame)
- sliceZero(p.nonce)
-}
-
-var (
- taps = make(map[string]*TAP)
-)
-
-// 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, timeout time.Duration, cpr int) (*TAP, chan []byte, chan struct{}, chan struct{}, error) {
- var tap *TAP
- var err error
- tap, exists := taps[ifaceName]
- if !exists {
- tap, err = NewTAP(ifaceName)
- if err != nil {
- return nil, nil, nil, nil, err
- }
- taps[ifaceName] = tap
- }
- sink := make(chan []byte)
- sinkReady := make(chan struct{})
- sinkTerminate := make(chan struct{})
- sinkSkip := make(chan struct{})
-
- go func() {
- cprCycle := cprCycleCalculate(cpr)
- if cprCycle != time.Duration(0) {
- timeout = cprCycle
- } else {
- timeout = timeout / TimeoutHeartbeat
- }
- heartbeat := time.Tick(timeout)
- var pkt []byte
- ListenCycle:
- for {
- select {
- case <-sinkTerminate:
- break ListenCycle
- case <-heartbeat:
- go func() { sink <- make([]byte, 0) }()
- continue
- case <-sinkSkip:
- case <-sinkReady:
- tap.ready <- struct{}{}
- tap.synced = true
- }
- HeartbeatCatched:
- select {
- case <-heartbeat:
- go func() { sink <- make([]byte, 0) }()
- goto HeartbeatCatched
- case <-sinkTerminate:
- break ListenCycle
- case pkt = <-tap.sink:
- tap.synced = false
- sink <- pkt
- }
- }
- close(sink)
- close(sinkReady)
- close(sinkTerminate)
- }()
- if exists && tap.synced {
- sinkSkip <- struct{}{}
- } else {
- sinkReady <- struct{}{}
- }
- return tap, sink, sinkReady, sinkTerminate, nil
-}
-
-func newNonceCipher(key *[32]byte) *xtea.Cipher {
- nonceKey := make([]byte, 16)
- salsa20.XORKeyStream(
- nonceKey,
- make([]byte, 32),
- make([]byte, xtea.BlockSize),
- key,
- )
- ciph, err := xtea.NewCipher(nonceKey)
- if err != nil {
- panic(err)
- }
- 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(isClient bool, addr string, conn RemoteConn, conf *PeerConf, key *[SSize]byte) *Peer {
- now := time.Now()
- 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,
- Conn: conn,
- Timeout: timeout,
- Established: now,
- LastPing: now,
- Id: conf.Id,
- NoiseEnable: noiseEnable,
- CPR: conf.CPR,
- CPRCycle: cprCycle,
- NonceRecv: 0,
- Key: key,
- NonceCipher: newNonceCipher(key),
- buf: make([]byte, MTU+S20BS+NonceSize+poly1305.TagSize),
- tag: new([poly1305.TagSize]byte),
- keyAuth: new([SSize]byte),
- nonce: make([]byte, NonceSize),
- }
- if isClient {
- peer.NonceOur = 1
- peer.nonceExpect = 0 + 2
- } else {
- peer.NonceOur = 0
- peer.nonceExpect = 1 + 2
- }
- if conn.Reorderable() {
- peer.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
- peer.nonceBucket1 = make(map[uint64]struct{}, NonceBucketSize)
- }
- return &peer
-}
-
-// Process incoming UDP packet.
-// ConnListen'es synchronization channel used to tell him that he is
-// free to receive new packets. Authenticated and decrypted packets
-// will be written to the interface immediately (except heartbeat ones).
-func (p *Peer) PktProcess(data []byte, tap io.Writer, ready chan struct{}) bool {
- p.size = len(data)
- p.frame = make([]byte, p.size)
- copy(p.frame, data)
- ready <- struct{}{}
-
- copy(p.buf[S20BS:], p.frame[:p.size-NonceSize-poly1305.TagSize])
- for i := 0; i < S20BS; i++ {
- p.buf[i] = byte(0)
- }
- salsa20.XORKeyStream(
- p.buf[:S20BS+p.size-NonceSize-poly1305.TagSize],
- p.buf[:S20BS+p.size-NonceSize-poly1305.TagSize],
- p.frame[p.size-NonceSize-poly1305.TagSize:p.size-poly1305.TagSize],
- p.Key,
- )
- copy(p.tag[:], p.frame[p.size-poly1305.TagSize:])
- copy(p.keyAuth[:], p.buf[:SSize])
- if !poly1305.Verify(p.tag, p.frame[:p.size-poly1305.TagSize], p.keyAuth) {
- p.FramesUnauth++
- return false
- }
-
- // Check if received nonce is known to us in either of two buckets.
- // If yes, then this is ignored duplicate.
- // Check from the oldest bucket, as in most cases this will result
- // in constant time check.
- // If Bucket0 is filled, then it becomes Bucket1.
- p.NonceCipher.Decrypt(
- p.nonce,
- p.frame[p.size-NonceSize-poly1305.TagSize:p.size-poly1305.TagSize],
- )
-
- p.nonceRecv = binary.BigEndian.Uint64(p.nonce)
- if p.Conn.Reorderable() {
- if _, p.nonceFound = p.nonceBucket1[p.NonceRecv]; p.nonceFound {
- p.FramesDup++
- return false
- }
- if _, p.nonceFound = p.nonceBucket0[p.NonceRecv]; p.nonceFound {
- p.FramesDup++
- return false
- }
- p.nonceBucket0[p.NonceRecv] = struct{}{}
- p.nonceBucketN++
- if p.nonceBucketN == NonceBucketSize {
- p.nonceBucket1 = p.nonceBucket0
- p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
- p.nonceBucketN = 0
- }
- } else {
- if p.nonceRecv != p.nonceExpect {
- p.FramesDup++
- return false
- }
- p.nonceExpect += 2
- }
-
- p.FramesIn++
- p.BytesIn += int64(p.size)
- p.LastPing = time.Now()
- p.NonceRecv = p.nonceRecv
- p.pktSize = binary.BigEndian.Uint16(p.buf[S20BS : S20BS+PktSizeSize])
- if p.pktSize == 0 {
- p.HeartbeatRecv++
- return true
- }
- p.BytesPayloadIn += int64(p.pktSize)
- tap.Write(p.buf[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSize])
- return true
-}
-
-// Process incoming Ethernet packet.
-// ready channel is TAPListen's synchronization channel used to tell him
-// that he is free to receive new packets. Encrypted and authenticated
-// packets will be sent to remote Peer side immediately.
-func (p *Peer) EthProcess(data []byte, ready chan struct{}) {
- p.now = time.Now()
- p.size = len(data)
- // If this heartbeat is necessary
- if p.size == 0 && !p.LastSent.Add(p.Timeout).Before(p.now) {
- return
- }
- if p.size > 0 {
- copy(p.buf[S20BS+PktSizeSize:], data)
- ready <- struct{}{}
- binary.BigEndian.PutUint16(p.buf[S20BS:S20BS+PktSizeSize], uint16(p.size))
- p.BytesPayloadOut += int64(p.size)
- } else {
- p.buf[S20BS+0] = byte(0)
- p.buf[S20BS+1] = byte(0)
- p.HeartbeatSent++
- }
-
- p.NonceOur += 2
- binary.BigEndian.PutUint64(p.nonce, p.NonceOur)
- p.NonceCipher.Encrypt(p.nonce, p.nonce)
-
- for i := 0; i < S20BS; i++ {
- p.buf[i] = byte(0)
- }
- salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key)
- if p.NoiseEnable {
- p.frame = append(p.buf[S20BS:S20BS+MTU-NonceSize-poly1305.TagSize], p.nonce...)
- } else {
- p.frame = append(p.buf[S20BS:S20BS+PktSizeSize+p.size], p.nonce...)
- }
- copy(p.keyAuth[:], p.buf[:SSize])
- poly1305.Sum(p.tag, p.frame, p.keyAuth)
-
- p.BytesOut += int64(len(p.frame) + poly1305.TagSize)
- p.FramesOut++
-
- if p.CPRCycle != time.Duration(0) {
- p.willSentCycle = p.LastSent.Add(p.CPRCycle)
- if p.willSentCycle.After(p.now) {
- time.Sleep(p.willSentCycle.Sub(p.now))
- p.now = p.willSentCycle
- }
- }
- p.LastSent = p.now
- p.Conn.Write(append(p.frame, p.tag[:]...))
-}