From 002ab2a45e2050f697157b50a776d35685e1a20b Mon Sep 17 00:00:00 2001 From: Bruno Clermont Date: Wed, 8 Feb 2017 18:52:55 +0800 Subject: [PATCH] Refactor server - server can be used as a Go library - unexposed type and values that aren't consumed outside package - golint fixes - switch logging to logrus - add more logging messages - improve usage as a library: no more `panic` or `os.Exit`, return `error` instead - evalute/raise nearly all possible `error` values - use interface to allow library consumer to use custom peer configuration backend --- .../govpn/cmd/govpn-server/action.go | 57 +++++ .../govpn/cmd/govpn-server/common.go | 74 ------ .../govpn/cmd/govpn-server/conf.go | 83 +++++-- .../govpn/cmd/govpn-server/main.go | 121 +++------ .../govpn/cmd/govpn-server/proxy.go | 46 ---- .../govpn/cmd/govpn-server/tcp.go | 200 --------------- .../govpn/cmd/govpn-server/udp.go | 205 --------------- src/cypherpunks.ru/govpn/server/common.go | 115 +++++++++ src/cypherpunks.ru/govpn/server/proxy.go | 64 +++++ src/cypherpunks.ru/govpn/server/server.go | 218 ++++++++++++++++ src/cypherpunks.ru/govpn/server/tcp.go | 217 ++++++++++++++++ src/cypherpunks.ru/govpn/server/udp.go | 235 ++++++++++++++++++ 12 files changed, 998 insertions(+), 637 deletions(-) create mode 100644 src/cypherpunks.ru/govpn/cmd/govpn-server/action.go delete mode 100644 src/cypherpunks.ru/govpn/cmd/govpn-server/common.go delete mode 100644 src/cypherpunks.ru/govpn/cmd/govpn-server/proxy.go delete mode 100644 src/cypherpunks.ru/govpn/cmd/govpn-server/tcp.go delete mode 100644 src/cypherpunks.ru/govpn/cmd/govpn-server/udp.go create mode 100644 src/cypherpunks.ru/govpn/server/common.go create mode 100644 src/cypherpunks.ru/govpn/server/proxy.go create mode 100644 src/cypherpunks.ru/govpn/server/server.go create mode 100644 src/cypherpunks.ru/govpn/server/tcp.go create mode 100644 src/cypherpunks.ru/govpn/server/udp.go diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/action.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/action.go new file mode 100644 index 0000000..9c25426 --- /dev/null +++ b/src/cypherpunks.ru/govpn/cmd/govpn-server/action.go @@ -0,0 +1,57 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 Sergey Matveev + +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 . +*/ + +package main + +import ( + "bytes" + + "github.com/pkg/errors" + + "cypherpunks.ru/govpn" +) + +func preUpAction(path string) govpn.TunnelPreUpAction { + if len(path) == 0 { + return nil + } + + return func(ctx govpn.PeerContext) (*govpn.TAP, error) { + result, err := govpn.ScriptCall(path, ctx.Config.Iface, ctx.RemoteAddress) + if err != nil { + return nil, errors.Wrap(err, "govpn.ScriptCall") + } + if len(ctx.Config.Iface) == 0 { + sepIndex := bytes.Index(result, []byte{'\n'}) + if sepIndex < 0 { + sepIndex = len(result) + } + ctx.Config.Iface = string(result[:sepIndex]) + } + + if len(ctx.Config.Iface) == 0 { + return nil, errors.Errorf("Script %q didn't returned an interface name", path) + } + + tap, err := govpn.TAPListen(ctx.Config.Iface, ctx.Config.MTU) + if err != nil { + return nil, errors.Wrap(err, "govpn.TAPListen") + } + return tap, nil + } +} diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/common.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/common.go deleted file mode 100644 index ff108ad..0000000 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/common.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2017 Sergey Matveev - -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 . -*/ - -package main - -import ( - "bytes" - "sync" - - "cypherpunks.ru/govpn" -) - -// PeerState holds server side state of a single connecting/connected peer -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 = make(map[string]*PeerState) - peersLock sync.RWMutex - - peersByID = make(map[govpn.PeerID]string) - peersByIDLock sync.RWMutex - - knownPeers govpn.KnownPeers - kpLock sync.RWMutex -) - -func callUp(peerID *govpn.PeerID, remoteAddr string) (string, error) { - ifaceName := confs[*peerID].Iface - if confs[*peerID].Up != "" { - result, err := govpn.ScriptCall(confs[*peerID].Up, ifaceName, remoteAddr) - if err != nil { - govpn.Printf( - `[script-failed bind="%s" path="%s" err="%s"]`, - *bindAddr, - confs[*peerID].Up, - err, - ) - return "", err - } - if ifaceName == "" { - sepIndex := bytes.Index(result, []byte{'\n'}) - if sepIndex < 0 { - sepIndex = len(result) - } - ifaceName = string(result[:sepIndex]) - } - } - if ifaceName == "" { - govpn.Printf(`[tap-failed bind="%s" peer="%s"]`, *bindAddr, *peerID) - } - return ifaceName, nil -} diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go index 62eca02..80581ab 100644 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go +++ b/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go @@ -19,41 +19,64 @@ along with this program. If not, see . package main import ( - "errors" "io/ioutil" - "log" "time" + "github.com/Sirupsen/logrus" + "github.com/pkg/errors" "gopkg.in/yaml.v2" "cypherpunks.ru/govpn" ) -const ( - RefreshRate = time.Minute -) +const refreshRate = time.Minute var ( - confs map[govpn.PeerID]*govpn.PeerConf + confs peerConfigurations idsCache *govpn.MACCache + logger *logrus.Logger ) +type peerConfigurations map[govpn.PeerID]*govpn.PeerConf + +func (peerConfs peerConfigurations) Get(peerID govpn.PeerID) *govpn.PeerConf { + pc, exists := peerConfs[peerID] + if !exists { + return nil + } + return pc +} + +type peerConf struct { + Name string `yaml:"name"` + Iface string `yaml:"iface"` + MTU int `yaml:"mtu"` + Up string `yaml:"up"` + Down string `yaml:"down"` + TimeoutInt int `yaml:"timeout"` + Noise bool `yaml:"noise"` + CPR int `yaml:"cpr"` + Encless bool `yaml:"encless"` + TimeSync int `yaml:"timesync"` + VerifierRaw string `yaml:"verifier"` +} + func confRead() (*map[govpn.PeerID]*govpn.PeerConf, error) { data, err := ioutil.ReadFile(*confPath) if err != nil { - return nil, err + return nil, errors.Wrap(err, "ioutil.ReadFile") } - confsRaw := new(map[string]govpn.PeerConf) + confsRaw := new(map[string]peerConf) err = yaml.Unmarshal(data, confsRaw) if err != nil { - return nil, err + return nil, errors.Wrap(err, "yaml.Unmarshal") } confs := make(map[govpn.PeerID]*govpn.PeerConf, len(*confsRaw)) for name, pc := range *confsRaw { verifier, err := govpn.VerifierFromString(pc.VerifierRaw) if err != nil { - return nil, errors.New("Unable to decode verifier: " + err.Error()) + return nil, errors.Wrap(err, "govpn.VerifierFromString") } if pc.Encless { pc.Noise = true @@ -62,7 +85,11 @@ func confRead() (*map[govpn.PeerID]*govpn.PeerConf, error) { pc.MTU = govpn.MTUDefault } if pc.MTU > govpn.MTUMax { - govpn.Printf(`[mtu-high bind="%s" value="%d" overriden="%d"]`, *bindAddr, pc.MTU, govpn.MTUMax) + logger.WithFields(logrus.Fields{ + "bind": *bindAddr, + "previous_mtu": pc.MTU, + "new_mtu": govpn.MTUMax, + }).Warning("Overriden MTU") pc.MTU = govpn.MTUMax } conf := govpn.PeerConf{ @@ -71,42 +98,54 @@ func confRead() (*map[govpn.PeerID]*govpn.PeerConf, error) { Name: name, Iface: pc.Iface, MTU: pc.MTU, - Up: pc.Up, - Down: pc.Down, + PreUp: preUpAction(pc.Up), + Down: govpn.RunScriptAction(&pc.Down), Noise: pc.Noise, CPR: pc.CPR, Encless: pc.Encless, TimeSync: pc.TimeSync, } if pc.TimeoutInt <= 0 { - pc.TimeoutInt = govpn.TimeoutDefault + conf.Timeout = govpn.TimeoutDefault + } else { + conf.Timeout = time.Second * time.Duration(pc.TimeoutInt) } - conf.Timeout = time.Second * time.Duration(pc.TimeoutInt) confs[*verifier.ID] = &conf } return &confs, nil } func confRefresh() error { + fields := logrus.Fields{ + "func": "confRefresh", + } + logger.WithFields(fields).Debug("Check configuration file") newConfs, err := confRead() if err != nil { - govpn.Printf(`[conf-parse-failed bind="%s" err="%s"]`, *bindAddr, err) - return err + return errors.Wrap(err, "confRead") } confs = *newConfs - idsCache.Update(newConfs) + logger.WithFields(fields).WithField("newConfs", len(confs)).Debug("idsCache.Update") + if err = idsCache.Update(newConfs); err != nil { + return errors.Wrap(err, "idsCache.Update") + } + logger.WithFields(fields).Debug("Done") return nil } func confInit() { idsCache = govpn.NewMACCache() - if err := confRefresh(); err != nil { - log.Fatalln(err) + err := confRefresh() + fields := logrus.Fields{"func": "confInit"} + if err != nil { + logger.WithError(err).WithFields(fields).Fatal("Couldn't perform initial configuration read") } go func() { for { - time.Sleep(RefreshRate) - confRefresh() + time.Sleep(refreshRate) + if err = confRefresh(); err != nil { + logger.WithError(err).WithFields(fields).Error("Couldn't refresh configuration") + } } }() } diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/main.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/main.go index 2beb396..789acdc 100644 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/main.go +++ b/src/cypherpunks.ru/govpn/cmd/govpn-server/main.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2017 Sergey Matveev +Copyright (C) 2014-2016 Sergey Matveev 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 @@ -22,13 +22,11 @@ package main import ( "flag" "fmt" - "log" - "net" - "os" - "os/signal" - "time" + + "github.com/Sirupsen/logrus" "cypherpunks.ru/govpn" + "cypherpunks.ru/govpn/server" ) var ( @@ -41,9 +39,13 @@ var ( syslog = flag.Bool("syslog", false, "Enable logging to syslog") version = flag.Bool("version", false, "Print version information") warranty = flag.Bool("warranty", false, "Print warranty information") + logLevel = flag.String("log_level", "warning", "Log level") ) func main() { + var err error + fields := logrus.Fields{"func": "main"} + flag.Parse() if *warranty { fmt.Println(govpn.Warranty) @@ -53,101 +55,40 @@ func main() { fmt.Println(govpn.VersionGet()) return } - timeout := time.Second * time.Duration(govpn.TimeoutDefault) - log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) - log.Println(govpn.VersionGet()) - confInit() - knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer)) + logger, err = govpn.NewLogger(*logLevel, *syslog) + if err != nil { + logrus.WithFields(fields).WithError(err).Fatal("Couldn't initialize logging") + } + govpn.SetLogger(logger) if *egdPath != "" { - log.Println("Using", *egdPath, "EGD") + logger.WithField("egd_path", *egdPath).WithFields(fields).Debug("Init EGD") govpn.EGDInit(*egdPath) } - if *syslog { - govpn.SyslogEnable() - } + confInit() - switch *proto { - case "udp": - startUDP() - case "tcp": - startTCP() - case "all": - startUDP() - startTCP() - default: - log.Fatalln("Unknown protocol specified") + serverConfig := server.Configuration{ + BindAddress: *bindAddr, + ProxyAddress: *proxy, + Timeout: govpn.TimeoutDefault, + } + if serverConfig.Protocol, err = govpn.NewProtocolFromString(*proto); err != nil { + logger.WithError(err).WithFields(fields).WithField("proto", *proto).Fatal("Invalid protocol") + } + if err = serverConfig.Validate(); err != nil { + logger.WithError(err).WithFields(fields).Fatal("Invalid configuration") } - termSignal := make(chan os.Signal, 1) - signal.Notify(termSignal, os.Interrupt, os.Kill) - - hsHeartbeat := time.Tick(timeout) - go func() { <-hsHeartbeat }() + srv := server.NewServer(serverConfig, confs, idsCache, logger, govpn.CatchSignalShutdown()) if *stats != "" { - log.Println("Stats are going to listen on", *stats) - statsPort, err := net.Listen("tcp", *stats) - if err != nil { - log.Fatalln("Can not listen on stats port:", err) - } - go govpn.StatsProcessor(statsPort, &knownPeers) + go govpn.StatsProcessor(*stats, srv.KnownPeers()) } - if *proxy != "" { - go proxyStart() - } - govpn.BothPrintf(`[started bind="%s"]`, *bindAddr) - - var needsDeletion bool -MainCycle: - for { - select { - case <-termSignal: - govpn.BothPrintf(`[terminating bind="%s"]`, *bindAddr) - for _, ps := range peers { - govpn.ScriptCall( - confs[*ps.peer.ID].Down, - ps.tap.Name, - ps.peer.Addr, - ) - } - break MainCycle - case <-hsHeartbeat: - now := time.Now() - hsLock.Lock() - for addr, hs := range handshakes { - if hs.LastPing.Add(timeout).Before(now) { - govpn.Printf(`[handshake-delete bind="%s" addr="%s"]`, *bindAddr, addr) - hs.Zero() - delete(handshakes, addr) - } - } - 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 { - govpn.Printf(`[peer-delete bind="%s" peer="%s"]`, *bindAddr, ps.peer) - delete(peers, addr) - delete(knownPeers, addr) - delete(peersByID, *ps.peer.ID) - go govpn.ScriptCall( - confs[*ps.peer.ID].Down, - ps.tap.Name, - ps.peer.Addr, - ) - ps.terminator <- struct{}{} - } - } - hsLock.Unlock() - peersLock.Unlock() - peersByIDLock.Unlock() - kpLock.Unlock() - } + + go srv.MainCycle() + if err = <-srv.Error; err != nil { + logger.WithError(err).Fatal("Fatal error") } } diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/proxy.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/proxy.go deleted file mode 100644 index 87e3455..0000000 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/proxy.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2017 Sergey Matveev - -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 . -*/ - -package main - -import ( - "net/http" - - "cypherpunks.ru/govpn" -) - -type proxyHandler struct{} - -func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - conn, _, err := w.(http.Hijacker).Hijack() - if err != nil { - govpn.Printf(`[proxy-hijack-failed bind="%s" err="%s"]`, *bindAddr, err) - return - } - conn.Write([]byte("HTTP/1.0 200 OK\n\n")) - go handleTCP(conn) -} - -func proxyStart() { - govpn.BothPrintf(`[proxy-listen bind="%s" addr="%s"]`, *bindAddr, *proxy) - s := &http.Server{ - Addr: *proxy, - Handler: proxyHandler{}, - } - govpn.BothPrintf(`[proxy-finished bind="%s" result="%s"]`, *bindAddr, s.ListenAndServe()) -} diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/tcp.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/tcp.go deleted file mode 100644 index c36da3f..0000000 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/tcp.go +++ /dev/null @@ -1,200 +0,0 @@ -/* -GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2017 Sergey Matveev - -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 . -*/ - -package main - -import ( - "bytes" - "log" - "net" - "time" - - "cypherpunks.ru/govpn" -) - -func startTCP() { - bind, err := net.ResolveTCPAddr("tcp", *bindAddr) - if err != nil { - log.Fatalln("Can not resolve bind address:", err) - } - listener, err := net.ListenTCP("tcp", bind) - if err != nil { - log.Fatalln("Can not listen on TCP:", err) - } - govpn.BothPrintf(`[tcp-listen bind="%s"]`, *bindAddr) - go func() { - for { - conn, err := listener.AcceptTCP() - if err != nil { - govpn.Printf(`[tcp-accept-failed bind="%s" err="%s"]`, *bindAddr, err) - continue - } - go handleTCP(conn) - } - }() -} - -func handleTCP(conn net.Conn) { - addr := conn.RemoteAddr().String() - buf := make([]byte, govpn.EnclessEnlargeSize+2*govpn.MTUMax) - 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 { - if prev == len(buf) { - 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 := idsCache.Find(buf[:prev]) - if peerID == nil { - continue - } - if hs == nil { - conf = confs[*peerID] - if conf == nil { - govpn.Printf( - `[conf-get-failed bind="%s" peer="%s"]`, - *bindAddr, peerID.String(), - ) - break - } - hs = govpn.NewHandshake(addr, conn, conf) - } - peer = hs.Server(buf[:prev]) - prev = 0 - if peer == nil { - continue - } - hs.Zero() - govpn.Printf( - `[handshake-completed bind="%s" addr="%s" peer="%s"]`, - *bindAddr, addr, peerID.String(), - ) - peersByIDLock.RLock() - addrPrev, exists := peersByID[*peer.ID] - peersByIDLock.RUnlock() - if exists { - peersLock.Lock() - peers[addrPrev].terminator <- struct{}{} - tap = peers[addrPrev].tap - ps = &PeerState{ - peer: peer, - tap: tap, - terminator: make(chan struct{}), - } - go govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator) - peersByIDLock.Lock() - kpLock.Lock() - delete(peers, addrPrev) - delete(knownPeers, addrPrev) - peers[addr] = ps - knownPeers[addr] = &peer - peersByID[*peer.ID] = addr - peersLock.Unlock() - peersByIDLock.Unlock() - kpLock.Unlock() - govpn.Printf( - `[rehandshake-completed bind="%s" peer="%s"]`, - *bindAddr, peerID.String(), - ) - } else { - ifaceName, err := callUp(peer.ID, peer.Addr) - if err != nil { - peer = nil - break - } - tap, err = govpn.TAPListen(ifaceName, peer.MTU) - if err != nil { - govpn.Printf( - `[tap-failed bind="%s" peer="%s" err="%s"]`, - *bindAddr, peerID.String(), err, - ) - peer = nil - break - } - ps = &PeerState{ - peer: peer, - tap: tap, - terminator: make(chan struct{}, 1), - } - go govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator) - peersLock.Lock() - peersByIDLock.Lock() - kpLock.Lock() - peers[addr] = ps - peersByID[*peer.ID] = addr - knownPeers[addr] = &peer - peersLock.Unlock() - peersByIDLock.Unlock() - kpLock.Unlock() - govpn.Printf(`[peer-created bind="%s" peer="%s"]`, *bindAddr, peerID.String()) - } - break - } - if hs != nil { - hs.Zero() - } - if peer == nil { - return - } - - prev = 0 - var i int - for { - if prev == len(buf) { - 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: - if prev < govpn.MinPktLength { - continue - } - i = bytes.Index(buf[:prev], peer.NonceExpect) - if i == -1 { - continue - } - if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) { - govpn.Printf( - `[packet-unauthenticated bind="%s" addr="%s" peer="%s"]`, - *bindAddr, addr, peer.ID.String(), - ) - break - } - copy(buf, buf[i+govpn.NonceSize:prev]) - prev = prev - i - govpn.NonceSize - goto CheckMore - } - peer.Zero() -} diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/udp.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/udp.go deleted file mode 100644 index cdcfb56..0000000 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/udp.go +++ /dev/null @@ -1,205 +0,0 @@ -/* -GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2017 Sergey Matveev - -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 . -*/ - -package main - -import ( - "log" - "net" - - "cypherpunks.ru/govpn" -) - -type UDPSender struct { - conn *net.UDPConn - addr *net.UDPAddr -} - -func (c UDPSender) Write(data []byte) (int, error) { - return c.conn.WriteToUDP(data, c.addr) -} - -var ( - // Buffers for UDP parallel processing - udpBufs = make(chan []byte, 1<<8) -) - -func startUDP() { - bind, err := net.ResolveUDPAddr("udp", *bindAddr) - if err != nil { - log.Fatalln("Can not resolve bind address:", err) - } - conn, err := net.ListenUDP("udp", bind) - if err != nil { - log.Fatalln("Can not listen on UDP:", err) - } - govpn.BothPrintf(`[udp-listen bind="%s"]`, *bindAddr) - - udpBufs <- make([]byte, govpn.MTUMax) - go func() { - 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 conf *govpn.PeerConf - for { - buf = <-udpBufs - n, raddr, err = conn.ReadFromUDP(buf) - if err != nil { - govpn.Printf(`[receive-failed bind="%s" err="%s"]`, *bindAddr, err) - break - } - addr = raddr.String() - - peersLock.RLock() - ps, exists = peers[addr] - peersLock.RUnlock() - if exists { - go func(peer *govpn.Peer, tap *govpn.TAP, buf []byte, n int) { - peer.PktProcess(buf[:n], tap, true) - udpBufs <- buf - }(ps.peer, ps.tap, buf, n) - continue - } - - hsLock.RLock() - hs, exists = handshakes[addr] - hsLock.RUnlock() - if !exists { - peerID = idsCache.Find(buf[:n]) - if peerID == nil { - govpn.Printf(`[identity-unknown bind="%s" addr="%s"]`, *bindAddr, addr) - udpBufs <- buf - continue - } - conf = confs[*peerID] - if conf == nil { - govpn.Printf( - `[conf-get-failed bind="%s" peer="%s"]`, - *bindAddr, peerID.String(), - ) - udpBufs <- buf - continue - } - hs := govpn.NewHandshake( - addr, - UDPSender{conn: conn, addr: raddr}, - conf, - ) - hs.Server(buf[:n]) - udpBufs <- buf - hsLock.Lock() - handshakes[addr] = hs - hsLock.Unlock() - continue - } - - peer := hs.Server(buf[:n]) - if peer == nil { - udpBufs <- buf - continue - } - govpn.Printf( - `[handshake-completed bind="%s" addr="%s" peer="%s"]`, - *bindAddr, addr, peerID.String(), - ) - hs.Zero() - hsLock.Lock() - delete(handshakes, addr) - hsLock.Unlock() - - go func() { - udpBufs <- make([]byte, govpn.MTUMax) - udpBufs <- make([]byte, govpn.MTUMax) - }() - peersByIDLock.RLock() - addrPrev, exists = peersByID[*peer.ID] - peersByIDLock.RUnlock() - if exists { - peersLock.Lock() - peers[addrPrev].terminator <- struct{}{} - psNew := &PeerState{ - peer: peer, - tap: peers[addrPrev].tap, - terminator: make(chan struct{}), - } - go func(peer *govpn.Peer, tap *govpn.TAP, terminator chan struct{}) { - govpn.PeerTapProcessor(peer, tap, terminator) - <-udpBufs - <-udpBufs - }(psNew.peer, psNew.tap, psNew.terminator) - peersByIDLock.Lock() - kpLock.Lock() - delete(peers, addrPrev) - delete(knownPeers, addrPrev) - peers[addr] = psNew - knownPeers[addr] = &peer - peersByID[*peer.ID] = addr - peersLock.Unlock() - peersByIDLock.Unlock() - kpLock.Unlock() - govpn.Printf( - `[rehandshake-completed bind="%s" peer="%s"]`, - *bindAddr, peer.ID.String(), - ) - } else { - go func(addr string, peer *govpn.Peer) { - ifaceName, err := callUp(peer.ID, peer.Addr) - if err != nil { - return - } - tap, err := govpn.TAPListen(ifaceName, peer.MTU) - if err != nil { - govpn.Printf( - `[tap-failed bind="%s" peer="%s" err="%s"]`, - *bindAddr, peer.ID.String(), err, - ) - return - } - psNew := &PeerState{ - peer: peer, - tap: tap, - terminator: make(chan struct{}), - } - go func(peer *govpn.Peer, tap *govpn.TAP, terminator chan struct{}) { - govpn.PeerTapProcessor(peer, tap, terminator) - <-udpBufs - <-udpBufs - }(psNew.peer, psNew.tap, psNew.terminator) - peersLock.Lock() - peersByIDLock.Lock() - kpLock.Lock() - peers[addr] = psNew - knownPeers[addr] = &peer - peersByID[*peer.ID] = addr - peersLock.Unlock() - peersByIDLock.Unlock() - kpLock.Unlock() - govpn.Printf(`[peer-created bind="%s" peer="%s"]`, *bindAddr, peer.ID.String()) - }(addr, peer) - } - udpBufs <- buf - } - }() -} diff --git a/src/cypherpunks.ru/govpn/server/common.go b/src/cypherpunks.ru/govpn/server/common.go new file mode 100644 index 0000000..cb2d679 --- /dev/null +++ b/src/cypherpunks.ru/govpn/server/common.go @@ -0,0 +1,115 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 Sergey Matveev + +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 . +*/ + +package server + +import ( + "github.com/pkg/errors" + + "cypherpunks.ru/govpn" +) + +const logFuncPrefix = "govpn/server." + +var ( + errMisconfiguredTap = errors.New("No PreUp and no Iface, can't create interface") + errPreUpNoTap = errors.New("PreUp didn't returned an interface, and Iface is unset") +) + +func (s *Server) callUp(peer *govpn.Peer, proto govpn.Protocol) (*govpn.TAP, error) { + var ( + tap *govpn.TAP + conf = s.confs.Get(*peer.ID) + err error + isConfigIface = len(conf.Iface) != 0 + fields = s.LogFields() + ) + fields["func"] = logFuncPrefix + "Server.callUp" + + if !isConfigIface && conf.PreUp == nil { + return nil, errors.Wrapf(errMisconfiguredTap, "interface:%q, PreUp:%q", conf.Iface, conf.PreUp) + } + + if conf.PreUp != nil { + s.logger.WithFields(fields).Debug("PreUp defined, execute it") + tap, err = conf.PreUp(govpn.PeerContext{ + RemoteAddress: peer.Addr, + Protocol: proto, + Config: *conf, + }) + if err != nil { + return nil, errors.Wrap(err, "conf.PreUp") + } + s.logger.WithFields(fields).WithField("tap", tap).Debug("PreUp finished") + } else { + s.logger.WithFields(fields).Debug("No PreUp defined, skip") + } + + if tap == nil { + s.logger.WithFields(fields).Debug("PreUp didn't returned an interface, create one") + if !isConfigIface { + return nil, errors.Wrapf(errPreUpNoTap, "interface:%q tap:%q", conf.Iface, tap) + } + + if tap, err = govpn.TAPListen(conf.Iface, peer.MTU); err != nil { + return nil, errors.Wrap(err, "govpn.TAPListen") + } + } + + conf.Iface = tap.Name + + if conf.Up == nil { + s.logger.WithFields(fields).Debug("Got interface, no Up") + return tap, nil + } + s.logger.WithFields(fields).Debug("Got interface, execute Up") + + err = conf.Up(govpn.PeerContext{ + RemoteAddress: peer.Addr, + Protocol: proto, + Config: *conf, + }) + if err != nil { + return nil, errors.Wrap(err, "conf.Up") + } + s.logger.WithFields(fields).Debug("Got interface, Up executed") + return tap, nil +} + +func (s *Server) callDown(ps *PeerState) error { + fields := s.LogFields() + fields["func"] = logFuncPrefix + "Server.callDown" + + conf := s.confs.Get(*ps.peer.ID) + if conf == nil { + s.logger.WithFields(fields).Error("Couldn't get configuration") + return nil + } + if conf.Down == nil { + s.logger.WithFields(fields).Debug("No Down, skip") + return nil + } + s.logger.WithFields(fields).Debug("Execute Down") + err := conf.Down(govpn.PeerContext{ + RemoteAddress: ps.peer.Addr, + Config: *conf, + Protocol: ps.peer.Protocol, + }) + s.logger.WithFields(fields).Debug("Down executed") + return errors.Wrap(err, "peer.Down") +} diff --git a/src/cypherpunks.ru/govpn/server/proxy.go b/src/cypherpunks.ru/govpn/server/proxy.go new file mode 100644 index 0000000..ad0f75d --- /dev/null +++ b/src/cypherpunks.ru/govpn/server/proxy.go @@ -0,0 +1,64 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 Sergey Matveev + +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 . +*/ + +package server + +import ( + "net/http" + + "github.com/Sirupsen/logrus" +) + +type proxyHandler struct { + goVpnServer *Server +} + +func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + conn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + p.goVpnServer.logger.WithError(err).WithFields( + logrus.Fields{ + "func": logFuncPrefix + "proxyHandler.ServeHTTP", + "address": p.goVpnServer.configuration.BindAddress, + }, + ).Error("Proxy hijack failed") + return + } + conn.Write([]byte("HTTP/1.0 200 OK\n\n")) + go p.goVpnServer.handleTCP(conn) +} + +func (s *Server) proxyStart() { + fields := logrus.Fields{ + "func": logFuncPrefix + "Server.proxyStart", + "address": s.configuration.BindAddress, + "proxy": s.configuration.ProxyAddress, + } + s.logger.WithFields(fields).Info("Proxy Listen") + httpServer := &http.Server{ + Addr: s.configuration.ProxyAddress, + Handler: proxyHandler{ + goVpnServer: s, + }, + } + if err := httpServer.ListenAndServe(); err != nil { + s.logger.WithFields(fields).WithError(err).Error("Proxy failed") + return + } + s.logger.WithFields(fields).Info("Proxy finished") +} diff --git a/src/cypherpunks.ru/govpn/server/server.go b/src/cypherpunks.ru/govpn/server/server.go new file mode 100644 index 0000000..9dfa032 --- /dev/null +++ b/src/cypherpunks.ru/govpn/server/server.go @@ -0,0 +1,218 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 Sergey Matveev + +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 . +*/ + +package server + +import ( + "sync" + "time" + + "github.com/Sirupsen/logrus" + "github.com/pkg/errors" + + "cypherpunks.ru/govpn" +) + +// PeerConfigurer is used by a GoVPN server to figure the configuration of a single peer +type PeerConfigurer interface { + Get(govpn.PeerID) *govpn.PeerConf +} + +// MACPeerFinder is used by GoVPN server to figure the PeerID from handshake data +type MACPeerFinder interface { + Find([]byte) (*govpn.PeerID, error) +} + +// PeerState hold server side state of a single connecting/connected peer +type PeerState struct { + peer *govpn.Peer + terminator chan struct{} + tap *govpn.TAP +} + +// Configuration hold GoVPN server configuration +type Configuration struct { + BindAddress string + ProxyAddress string + Protocol govpn.Protocol + Timeout time.Duration +} + +// Validate return an error if a configuration is invalid +func (c *Configuration) Validate() error { + if len(c.BindAddress) == 0 { + return errors.New("Missing BindAddress") + } + return nil +} + +// LogFields return a logrus compatible logging context +func (c *Configuration) LogFields() logrus.Fields { + const prefix = "srv_conf_" + f := logrus.Fields{ + prefix + "bind": c.BindAddress, + prefix + "protocol": c.Protocol.String(), + prefix + "timeout": c.Timeout.String(), + } + if len(c.ProxyAddress) > 0 { + f[prefix+"proxy"] = c.ProxyAddress + } + return f +} + +// Server is a GoVPN server instance +type Server struct { + configuration Configuration + termSignal chan interface{} + + idsCache MACPeerFinder + confs PeerConfigurer + + handshakes map[string]*govpn.Handshake + hsLock sync.RWMutex + + peers map[string]*PeerState + peersLock sync.RWMutex + + peersByID map[govpn.PeerID]string + peersByIDLock sync.RWMutex + + knownPeers govpn.KnownPeers + kpLock sync.RWMutex + + logger *logrus.Logger + + // Error channel receives any kind of routine errors + Error chan error +} + +// LogFields return a logrus compatible logging context +func (s *Server) LogFields() logrus.Fields { + const prefix = "srv_" + return logrus.Fields{ + prefix + "hs": len(s.handshakes), + prefix + "peers": len(s.peers), + prefix + "known": len(s.knownPeers), + } +} + +// KnownPeers return GoVPN peers. +// used to get client statistics. +func (s *Server) KnownPeers() *govpn.KnownPeers { + return &s.knownPeers +} + +// NewServer return a configured GoVPN server, to listen network connection MainCycle must be executed +func NewServer(serverConf Configuration, peerConfs PeerConfigurer, idsCache MACPeerFinder, logger *logrus.Logger, termSignal chan interface{}) *Server { + govpn.SetLogger(logger) + return &Server{ + configuration: serverConf, + confs: peerConfs, + termSignal: termSignal, + idsCache: idsCache, + handshakes: make(map[string]*govpn.Handshake), + peers: make(map[string]*PeerState), + peersByID: make(map[govpn.PeerID]string), + knownPeers: govpn.KnownPeers(make(map[string]**govpn.Peer)), + Error: make(chan error, 1), + logger: logger, + } +} + +// MainCycle main loop that handle connecting/connected client +func (s *Server) MainCycle() { + switch s.configuration.Protocol { + case govpn.ProtocolUDP: + s.startUDP() + case govpn.ProtocolTCP: + s.startTCP() + case govpn.ProtocolALL: + s.startUDP() + s.startTCP() + default: + s.Error <- errors.New("Unknown protocol specified") + return + } + + if len(s.configuration.ProxyAddress) > 0 { + go s.proxyStart() + } + fields := logrus.Fields{"func": logFuncPrefix + "Server.MainCycle"} + + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(s.configuration.LogFields()).Info("Starting...") + + var needsDeletion bool + var err error + hsHeartbeat := time.Tick(s.configuration.Timeout) + go func() { <-hsHeartbeat }() + +MainCycle: + for { + select { + case <-s.termSignal: + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(s.configuration.LogFields()).Info("Terminating") + for _, ps := range s.peers { + if err = s.callDown(ps); err != nil { + s.logger.WithFields(fields).WithError(err).WithFields(ps.peer.LogFields()).Error("Failed to run callDown") + } + if err = ps.tap.Close(); err != nil { + logrus.WithError(err).WithFields(fields).WithFields(ps.peer.LogFields()).Error("Couldn't close TAP") + } + } + // empty value signals that everything is fine + s.Error <- nil + break MainCycle + case <-hsHeartbeat: + logrus.WithFields(fields).Debug("Heartbeat") + now := time.Now() + s.hsLock.Lock() + for addr, hs := range s.handshakes { + if hs.LastPing.Add(s.configuration.Timeout).Before(now) { + logrus.WithFields(fields).WithFields(hs.LogFields()).Debug("handshake is expired, delete") + hs.Zero() + delete(s.handshakes, addr) + } + } + s.peersLock.Lock() + s.peersByIDLock.Lock() + s.kpLock.Lock() + for addr, ps := range s.peers { + ps.peer.BusyR.Lock() + needsDeletion = ps.peer.LastPing.Add(s.configuration.Timeout).Before(now) + ps.peer.BusyR.Unlock() + if needsDeletion { + logrus.WithFields(fields).WithFields(ps.peer.LogFields()).Info("Delete peer") + delete(s.peers, addr) + delete(s.knownPeers, addr) + delete(s.peersByID, *ps.peer.ID) + if err = s.callDown(ps); err != nil { + logrus.WithError(err).WithFields(fields).WithFields(ps.peer.LogFields()).Error("Couldn't execute callDown") + } + if err = ps.tap.Close(); err != nil { + logrus.WithError(err).WithFields(fields).WithFields(ps.peer.LogFields()).Error("Couldn't close TAP") + } + ps.terminator <- struct{}{} + } + } + s.hsLock.Unlock() + s.peersLock.Unlock() + s.peersByIDLock.Unlock() + s.kpLock.Unlock() + } + } +} diff --git a/src/cypherpunks.ru/govpn/server/tcp.go b/src/cypherpunks.ru/govpn/server/tcp.go new file mode 100644 index 0000000..34a78fc --- /dev/null +++ b/src/cypherpunks.ru/govpn/server/tcp.go @@ -0,0 +1,217 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 Sergey Matveev + +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 . +*/ + +package server + +import ( + "bytes" + "net" + "time" + + "github.com/Sirupsen/logrus" + "github.com/pkg/errors" + + "cypherpunks.ru/govpn" +) + +func (s *Server) startTCP() { + bind, err := net.ResolveTCPAddr("tcp", s.configuration.BindAddress) + if err != nil { + s.Error <- errors.Wrap(err, "net.ResolveTCPAddr") + return + } + listener, err := net.ListenTCP("tcp", bind) + if err != nil { + s.Error <- errors.Wrapf(err, "net.ListenTCP %q", bind.String()) + return + } + fields := logrus.Fields{ + "func": logFuncPrefix + "Server.startTCP", + "bind": bind.String(), + } + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(s.configuration.LogFields()).Info("Listen") + go func() { + for { + conn, err := listener.AcceptTCP() + if err != nil { + s.logger.WithError(err).WithFields(fields).WithFields(s.LogFields()).Error("Failed to accept TCP connection") + continue + } + go s.handleTCP(conn) + } + }() +} + +func (s *Server) handleTCP(conn net.Conn) { + addr := conn.RemoteAddr().String() + buf := make([]byte, govpn.EnclessEnlargeSize+2*govpn.MTUMax) + var n int + var err error + var prev int + var hs *govpn.Handshake + var ps *PeerState + var peer *govpn.Peer + var deadLine time.Time + var tap *govpn.TAP + var conf *govpn.PeerConf + fields := logrus.Fields{ + "func": logFuncPrefix + "Server.handleTCP", + "remote": addr, + } + for { + if prev == len(buf) { + // TODO log why + break + } + + deadLine = time.Now().Add(govpn.TimeoutDefault) + if err = conn.SetReadDeadline(deadLine); err != nil { + s.Error <- errors.Wrapf(err, "conn.SetReadDeadline %s", deadLine.String()) + return + } + n, err = conn.Read(buf[prev:]) + if err != nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithError(err).Debug("Can't read connection: either EOFed or timeouted") + break + } + prev += n + peerID, err := s.idsCache.Find(buf[:prev]) + if err != nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithError(err).Debug("Couldn't lookup for peer in ids") + continue + } + if peerID == nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).Debug("Couldn't find peer") + continue + } + if hs == nil { + conf = s.confs.Get(*peerID) + if conf == nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(s.configuration.LogFields()).Error("Configuration get failed") + break + } + hs = govpn.NewHandshake(addr, conn, conf) + } + peer, err = hs.Server(buf[:prev]) + if err != nil { + s.logger.WithFields(fields).WithError(err).WithFields(s.LogFields()).Error("Can't create new peer") + continue + } + prev = 0 + if peer == nil { + continue + } + + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).Info("Handshake completed") + + hs.Zero() + s.peersByIDLock.RLock() + addrPrev, exists := s.peersByID[*peer.ID] + s.peersByIDLock.RUnlock() + + if exists { + s.peersLock.Lock() + s.peers[addrPrev].terminator <- struct{}{} + tap = s.peers[addrPrev].tap + ps = &PeerState{ + peer: peer, + tap: tap, + terminator: make(chan struct{}), + } + peer.Protocol = govpn.ProtocolTCP + go govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator) + s.peersByIDLock.Lock() + s.kpLock.Lock() + delete(s.peers, addrPrev) + delete(s.knownPeers, addrPrev) + s.peers[addr] = ps + s.knownPeers[addr] = &peer + s.peersByID[*peer.ID] = addr + s.peersLock.Unlock() + s.peersByIDLock.Unlock() + s.kpLock.Unlock() + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).Debug("Rehandshake completed") + } else { + tap, err = s.callUp(peer, govpn.ProtocolTCP) + if err != nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).WithError(err).Error("TAP failed") + peer = nil + break + } + ps = &PeerState{ + peer: peer, + tap: tap, + terminator: make(chan struct{}, 1), + } + peer.Protocol = govpn.ProtocolTCP + go govpn.PeerTapProcessor(ps.peer, ps.tap, ps.terminator) + s.peersLock.Lock() + s.peersByIDLock.Lock() + s.kpLock.Lock() + s.peers[addr] = ps + s.peersByID[*peer.ID] = addr + s.knownPeers[addr] = &peer + s.peersLock.Unlock() + s.peersByIDLock.Unlock() + s.kpLock.Unlock() + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).Info("Peer created") + } + break + } + if hs != nil { + hs.Zero() + } + if peer == nil { + return + } + + prev = 0 + var i int + for { + if prev == len(buf) { + break + } + deadLine = time.Now().Add(conf.Timeout) + if err = conn.SetReadDeadline(deadLine); err != nil { + s.Error <- errors.Wrapf(err, "conn.SetReadDeadline %s", deadLine.String()) + return + } + n, err = conn.Read(buf[prev:]) + if err != nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithError(err).Debug("Can't read connection: either EOFed or timeouted") + break + } + prev += n + CheckMore: + if prev < govpn.MinPktLength { + continue + } + i = bytes.Index(buf[:prev], peer.NonceExpect) + if i == -1 { + continue + } + if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).Warn("Packet unauthenticated") + break + } + copy(buf, buf[i+govpn.NonceSize:prev]) + prev = prev - i - govpn.NonceSize + goto CheckMore + } + peer.Zero() +} diff --git a/src/cypherpunks.ru/govpn/server/udp.go b/src/cypherpunks.ru/govpn/server/udp.go new file mode 100644 index 0000000..cd8c2fc --- /dev/null +++ b/src/cypherpunks.ru/govpn/server/udp.go @@ -0,0 +1,235 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 Sergey Matveev + +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 . +*/ + +package server + +import ( + "net" + + "github.com/Sirupsen/logrus" + "github.com/pkg/errors" + + "cypherpunks.ru/govpn" +) + +type udpSender struct { + conn *net.UDPConn + addr *net.UDPAddr +} + +func (c udpSender) Write(data []byte) (int, error) { + return c.conn.WriteToUDP(data, c.addr) +} + +// TODO move to udpSender (?) +// buffers for UDP parallel processing +var udpBufs = make(chan []byte, 1<<8) + +func (s *Server) startUDP() { + bind, err := net.ResolveUDPAddr("udp", s.configuration.BindAddress) + if err != nil { + s.Error <- errors.Wrap(err, "net.ResolveUDPAddr") + return + } + conn, err := net.ListenUDP("udp", bind) + if err != nil { + s.Error <- errors.Wrapf(err, "net.ListenUDP %q", bind.String()) + return + } + + fields := logrus.Fields{ + "func": logFuncPrefix + "Server.startUDP", + "bind": bind.String(), + } + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(s.configuration.LogFields()).Info("Listen") + udpBufs <- make([]byte, govpn.MTUMax) + go func() { + 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 conf *govpn.PeerConf + for { + s.logger.WithFields(fields).Debug("Wait for UDP buffer") + buf = <-udpBufs + n, raddr, err = conn.ReadFromUDP(buf) + if err != nil { + s.logger.WithFields(fields).WithFields(s.LogFields()).WithError(err).Debug("Receive failure") + break + } + addr = raddr.String() + loopFields := logrus.Fields{"addr": addr} + + s.logger.WithFields(fields).WithFields(loopFields).Debug("Got UDP buffer, check if peer exists") + s.peersLock.RLock() + ps, exists = s.peers[addr] + s.peersLock.RUnlock() + if exists { + s.logger.WithFields(fields).WithFields(loopFields).Debug("Already known peer, PktProcess") + go func(peer *govpn.Peer, tap *govpn.TAP, buf []byte, n int) { + peer.PktProcess(buf[:n], tap, true) + udpBufs <- buf + }(ps.peer, ps.tap, buf, n) + continue + } + + logrus.WithFields(fields).WithFields(loopFields).Debug("New peer") + s.hsLock.RLock() + hs, exists = s.handshakes[addr] + s.hsLock.RUnlock() + if !exists { + logrus.WithFields(fields).WithFields(loopFields).Debug("No handshake yet, try to figure peer ID") + peerID, err = s.idsCache.Find(buf[:n]) + if err != nil { + s.logger.WithFields(fields).WithFields(loopFields).WithFields(s.LogFields()).WithError(err).Debug("Couldn't lookup for peer in ids") + udpBufs <- buf + continue + } + if peerID == nil { + s.logger.WithFields(fields).WithFields(loopFields).WithFields(s.LogFields()).Debug("Identity unknown") + udpBufs <- buf + continue + } + + loopFields["peer_id"] = peerID.String() + s.logger.WithFields(fields).WithFields(loopFields).Debug("Found peer ID") + conf = s.confs.Get(*peerID) + if conf == nil { + s.logger.WithFields(loopFields).WithFields(fields).WithFields(s.LogFields()).WithFields(s.configuration.LogFields()).Error("Peer try to connect, but not configured") + udpBufs <- buf + continue + } + + s.logger.WithFields(loopFields).WithFields(fields).Debug("Got configuration, perform handshake") + hs = govpn.NewHandshake( + addr, + udpSender{conn: conn, addr: raddr}, + conf, + ) + _, err := hs.Server(buf[:n]) + udpBufs <- buf + if err != nil { + s.logger.WithFields(loopFields).WithFields(fields).WithError(err).WithFields(s.LogFields()).Error("Can't create new peer: handshake failed") + continue + } + s.logger.WithFields(loopFields).WithFields(fields).WithFields(s.LogFields()).Info("Hashshake started, continue next packet") + + s.hsLock.Lock() + s.handshakes[addr] = hs + s.hsLock.Unlock() + continue + } + + logrus.WithFields(fields).WithFields(loopFields).Debug("Already go handshake, finish it") + peer, err := hs.Server(buf[:n]) + if err != nil { + s.logger.WithFields(fields).WithFields(loopFields).WithError(err).WithFields(s.LogFields()).Error("Can't create new peer: handshake failed") + udpBufs <- buf + continue + } + if peer == nil { + s.logger.WithFields(fields).WithFields(loopFields).WithFields(s.LogFields()).Error("Couldn't continue handshake") + udpBufs <- buf + continue + } + + s.logger.WithFields(fields).WithFields(s.LogFields()).WithFields(loopFields).WithFields(peer.LogFields()).Info("Handshake completed") + + hs.Zero() + s.hsLock.Lock() + delete(s.handshakes, addr) + s.hsLock.Unlock() + + go func() { + udpBufs <- make([]byte, govpn.MTUMax) + udpBufs <- make([]byte, govpn.MTUMax) + }() + s.peersByIDLock.RLock() + addrPrev, exists = s.peersByID[*peer.ID] + s.peersByIDLock.RUnlock() + + if exists { + s.logger.WithFields(fields).WithFields(loopFields).Debug("Peer already exists") + s.peersLock.Lock() + s.peers[addrPrev].terminator <- struct{}{} + psNew := &PeerState{ + peer: peer, + tap: s.peers[addrPrev].tap, + terminator: make(chan struct{}), + } + peer.Protocol = govpn.ProtocolUDP + + go func(peer *govpn.Peer, tap *govpn.TAP, terminator chan struct{}) { + govpn.PeerTapProcessor(peer, tap, terminator) + <-udpBufs + <-udpBufs + }(psNew.peer, psNew.tap, psNew.terminator) + + s.peersByIDLock.Lock() + s.kpLock.Lock() + delete(s.peers, addrPrev) + delete(s.knownPeers, addrPrev) + s.peers[addr] = psNew + s.knownPeers[addr] = &peer + s.peersByID[*peer.ID] = addr + s.peersLock.Unlock() + s.peersByIDLock.Unlock() + s.kpLock.Unlock() + + s.logger.WithFields(fields).WithFields(loopFields).WithFields(s.LogFields()).WithFields(peer.LogFields()).Debug("Rehandshake completed") + } else { + go func(addr string, peer *govpn.Peer) { + s.logger.WithFields(fields).WithFields(loopFields).Debug("Peer do not already exists") + tap, err := s.callUp(peer, govpn.ProtocolUDP) + if err != nil { + s.logger.WithFields(loopFields).WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).WithError(err).Error("TAP failed") + return + } + psNew := &PeerState{ + peer: peer, + tap: tap, + terminator: make(chan struct{}), + } + peer.Protocol = govpn.ProtocolUDP + go func(peer *govpn.Peer, tap *govpn.TAP, terminator chan struct{}) { + govpn.PeerTapProcessor(peer, tap, terminator) + <-udpBufs + <-udpBufs + }(psNew.peer, psNew.tap, psNew.terminator) + s.peersLock.Lock() + s.peersByIDLock.Lock() + s.kpLock.Lock() + s.peers[addr] = psNew + s.knownPeers[addr] = &peer + s.peersByID[*peer.ID] = addr + s.peersLock.Unlock() + s.peersByIDLock.Unlock() + s.kpLock.Unlock() + s.logger.WithFields(loopFields).WithFields(fields).WithFields(s.LogFields()).WithFields(peer.LogFields()).Info("Peer initialized") + }(addr, peer) + } + udpBufs <- buf + } + }() +} -- 2.44.0