]> Cypherpunks.ru repositories - govpn.git/commitdiff
Refactor govpn-client.
authorBruno Clermont <bruno@robotinfra.com>
Thu, 8 Dec 2016 10:21:42 +0000 (18:21 +0800)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 8 Dec 2016 19:52:05 +0000 (22:52 +0300)
- move out of main client logic, allowing it to be imported from other Go code.
- evaluate all errors values
- client package only return `error`, it don't `os.Exit` anymore
- add a new `Protocol` type

src/cypherpunks.ru/govpn/client/client.go [new file with mode: 0644]
src/cypherpunks.ru/govpn/client/proxy.go [moved from src/cypherpunks.ru/govpn/cmd/govpn-client/proxy.go with 65% similarity]
src/cypherpunks.ru/govpn/client/tcp.go [moved from src/cypherpunks.ru/govpn/cmd/govpn-client/tcp.go with 52% similarity]
src/cypherpunks.ru/govpn/client/udp.go [moved from src/cypherpunks.ru/govpn/cmd/govpn-client/udp.go with 51% similarity]
src/cypherpunks.ru/govpn/cmd/govpn-client/main.go

diff --git a/src/cypherpunks.ru/govpn/client/client.go b/src/cypherpunks.ru/govpn/client/client.go
new file mode 100644 (file)
index 0000000..ac54a86
--- /dev/null
@@ -0,0 +1,136 @@
+package client
+
+import (
+       "fmt"
+       "net"
+       "os"
+       "errors"
+       "time"
+
+       "github.com/agl/ed25519"
+       "cypherpunks.ru/govpn"
+)
+
+type Protocol uint8
+
+const (
+       ProtocolUDP Protocol = iota
+       ProtocolTCP
+)
+
+type Configuration struct {
+       PrivateKey          *[ed25519.PrivateKeySize]byte
+       Peer                *govpn.PeerConf
+       Protocol            Protocol
+       InterfaceName       string
+       ProxyAddress        string
+       ProxyAuthentication string
+       RemoteAddress       string
+       UpPath, DownPath    string
+       StatsAddress        string
+       NoReconnect         bool
+       MTU                 int
+}
+
+func (c *Configuration) Validate() error {
+       if c.MTU > govpn.MTUMax {
+               return fmt.Errorf("Invalid MTU %d, maximum allowable is %d",c.MTU, govpn.MTUMax)
+       }
+       if len(c.RemoteAddress) == 0 {
+               return errors.New("Missing RemoteAddress")
+       }
+       if len(c.InterfaceName) == 0 {
+               return errors.New("Missing InterfaceName")
+       }
+       return nil
+}
+
+func (c *Configuration) isProxy() bool {
+       return len(c.ProxyAddress) > 0
+}
+
+type Client struct {
+       idsCache      *govpn.MACCache
+       tap           *govpn.TAP
+       knownPeers    govpn.KnownPeers
+       statsPort     net.Listener
+       timeouted     chan struct{}
+       rehandshaking chan struct{}
+       termination   chan struct{}
+       firstUpCall   bool
+       termSignal    chan os.Signal
+       config        Configuration
+
+       // Error receive any error of all routines
+       Error         chan error
+}
+
+func (c *Client) MainCycle() {
+       var err error
+       c.tap, err = govpn.TAPListen(c.config.InterfaceName, c.config.MTU)
+       if err != nil {
+               c.Error <- fmt.Errorf("Can not listen on TUN/TAP interface: %s", err.Error())
+               return
+       }
+
+       if len(c.config.StatsAddress) > 0 {
+               c.statsPort, err = net.Listen("tcp", c.config.StatsAddress)
+               if err != nil {
+                       c.Error <- fmt.Errorf("Can't listen on stats port: %s", err.Error())
+                       return
+               }
+               c.knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer))
+               go govpn.StatsProcessor(c.statsPort, &c.knownPeers)
+       }
+
+       MainCycle:
+       for {
+               c.timeouted = make(chan struct{})
+               c.rehandshaking = make(chan struct{})
+               c.termination = make(chan struct{})
+               switch c.config.Protocol {
+               case ProtocolUDP:
+                       go c.startUDP()
+               case ProtocolTCP:
+                       if c.config.isProxy() {
+                               go c.proxyTCP()
+                       } else {
+                               go c.startTCP()
+                       }
+               }
+               select {
+               case <-c.termSignal:
+                       govpn.BothPrintf(`[finish remote="%s"]`, c.config.RemoteAddress)
+                       c.termination <- struct{}{}
+                       // send a non-error to let know everything went fine
+                       c.Error <- nil
+                       break MainCycle
+               case <-c.timeouted:
+                       if c.config.NoReconnect {
+                               break MainCycle
+                       }
+                       govpn.BothPrintf(`[sleep seconds="%d"]`, c.config.Peer.Timeout)
+                       time.Sleep(c.config.Peer.Timeout)
+               case <-c.rehandshaking:
+               }
+               close(c.timeouted)
+               close(c.rehandshaking)
+               close(c.termination)
+       }
+       if _, err = govpn.ScriptCall(c.config.DownPath, c.config.InterfaceName, c.config.RemoteAddress); err != nil {
+               c.Error <- err
+       }
+}
+
+func NewClient(conf Configuration, verifier *govpn.Verifier, termSignal chan os.Signal) *Client {
+       client := &Client{
+               idsCache: govpn.NewMACCache(),
+               firstUpCall: true,
+               config: conf,
+               termSignal: termSignal,
+               Error: make(chan error, 1),
+       }
+       confs := map[govpn.PeerId]*govpn.PeerConf{*verifier.Id: conf.Peer}
+       client.idsCache.Update(&confs)
+       return client
+}
similarity index 65%
rename from src/cypherpunks.ru/govpn/cmd/govpn-client/proxy.go
rename to src/cypherpunks.ru/govpn/client/proxy.go
index 0a6729c1dd7ca99ae0ce8f66bc4793ace0bd8a3e..9f8e684868b073c02af62ad63fa1b4608a731728 100644 (file)
@@ -16,32 +16,34 @@ 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 main
+package client
 
 import (
        "bufio"
        "encoding/base64"
-       "log"
        "net"
+       "fmt"
        "net/http"
 
        "cypherpunks.ru/govpn"
 )
 
-func proxyTCP(timeouted, rehandshaking, termination chan struct{}) {
-       proxyAddr, err := net.ResolveTCPAddr("tcp", *proxyAddr)
+func (c *Client) proxyTCP() {
+       proxyAddr, err := net.ResolveTCPAddr("tcp", c.config.ProxyAddress)
        if err != nil {
-               log.Fatalln("Can not resolve proxy address:", err)
+               c.Error <- err
+               return
        }
        conn, err := net.DialTCP("tcp", nil, proxyAddr)
        if err != nil {
-               log.Fatalln("Can not connect to proxy:", err)
+               c.Error <- err
+               return
        }
-       req := "CONNECT " + *remoteAddr + " HTTP/1.1\n"
-       req += "Host: " + *remoteAddr + "\n"
-       if *proxyAuth != "" {
+       req := "CONNECT " + c.config.ProxyAddress + " HTTP/1.1\n"
+       req += "Host: " + c.config.ProxyAddress + "\n"
+       if c.config.ProxyAuthentication != "" {
                req += "Proxy-Authorization: Basic "
-               req += base64.StdEncoding.EncodeToString([]byte(*proxyAuth)) + "\n"
+               req += base64.StdEncoding.EncodeToString([]byte(c.config.ProxyAuthentication)) + "\n"
        }
        req += "\n"
        conn.Write([]byte(req))
@@ -50,8 +52,9 @@ func proxyTCP(timeouted, rehandshaking, termination chan struct{}) {
                &http.Request{Method: "CONNECT"},
        )
        if err != nil || resp.StatusCode != http.StatusOK {
-               log.Fatalln("Unexpected response from proxy")
+               c.Error <- fmt.Errorf("Unexpected response from proxy: %s", err.Error())
+               return
        }
-       govpn.Printf(`[proxy-connected remote="%s" addr="%s"]`, *remoteAddr, *proxyAddr)
-       go handleTCP(conn, timeouted, rehandshaking, termination)
+       govpn.Printf(`[proxy-connected remote="%s" addr="%s"]`, c.config.RemoteAddress, *proxyAddr)
+       go c.handleTCP(conn)
 }
similarity index 52%
rename from src/cypherpunks.ru/govpn/cmd/govpn-client/tcp.go
rename to src/cypherpunks.ru/govpn/client/tcp.go
index 57a412d31d687772c691fa2dc07cb7a65ddf8946..85e2ec3381a7564618d14e3b099af05e06e73e62 100644 (file)
@@ -16,11 +16,11 @@ 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 main
+package client
 
 import (
        "bytes"
-       "log"
+       "fmt"
        "net"
        "sync/atomic"
        "time"
@@ -28,22 +28,24 @@ import (
        "cypherpunks.ru/govpn"
 )
 
-func startTCP(timeouted, rehandshaking, termination chan struct{}) {
-       remote, err := net.ResolveTCPAddr("tcp", *remoteAddr)
+func (c *Client) startTCP() {
+       remote, err := net.ResolveTCPAddr("tcp", c.config.RemoteAddress)
        if err != nil {
-               log.Fatalln("Can not resolve remote address:", err)
+               c.Error <- fmt.Errorf("Can not resolve remote address: %s", err)
+               return
        }
        conn, err := net.DialTCP("tcp", nil, remote)
        if err != nil {
-               log.Fatalln("Can not connect to address:", err)
+               c.Error <- fmt.Errorf("Can not connect to address: %s", err)
+               return
        }
-       govpn.Printf(`[connected remote="%s"]`, *remoteAddr)
-       handleTCP(conn, timeouted, rehandshaking, termination)
+       govpn.Printf(`[connected remote="%s"]`, c.config.RemoteAddress)
+       c.handleTCP(conn)
 }
 
-func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan struct{}) {
-       hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
-       buf := make([]byte, 2*(govpn.EnclessEnlargeSize+*mtu)+*mtu)
+func (c *Client) handleTCP(conn *net.TCPConn) {
+       hs := govpn.HandshakeStart(c.config.RemoteAddress, conn, c.config.Peer)
+       buf := make([]byte, 2*(govpn.EnclessEnlargeSize+c.config.MTU)+c.config.MTU)
        var n int
        var err error
        var prev int
@@ -52,26 +54,29 @@ func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan str
 HandshakeCycle:
        for {
                select {
-               case <-termination:
+               case <-c.termination:
                        break HandshakeCycle
                default:
                }
                if prev == len(buf) {
-                       govpn.Printf(`[packet-timeouted remote="%s"]`, *remoteAddr)
-                       timeouted <- struct{}{}
+                       govpn.Printf(`[packet-timeouted remote="%s"]`, c.config.RemoteAddress)
+                       c.timeouted <- struct{}{}
                        break HandshakeCycle
                }
 
-               conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
+               if err = conn.SetReadDeadline(time.Now().Add(c.config.Peer.Timeout)); err != nil {
+                       c.Error <- err
+                       break HandshakeCycle
+               }
                n, err = conn.Read(buf[prev:])
                if err != nil {
-                       govpn.Printf(`[connection-timeouted remote="%s"]`, *remoteAddr)
-                       timeouted <- struct{}{}
+                       govpn.Printf(`[connection-timeouted remote="%s"]`, c.config.RemoteAddress)
+                       c.timeouted <- struct{}{}
                        break HandshakeCycle
                }
 
                prev += n
-               peerId := idsCache.Find(buf[:prev])
+               peerId := c.idsCache.Find(buf[:prev])
                if peerId == nil {
                        continue
                }
@@ -80,15 +85,15 @@ HandshakeCycle:
                if peer == nil {
                        continue
                }
-               govpn.Printf(`[handshake-completed remote="%s"]`, *remoteAddr)
-               knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
-               if firstUpCall {
-                       go govpn.ScriptCall(*upPath, *ifaceName, *remoteAddr)
-                       firstUpCall = false
+               govpn.Printf(`[handshake-completed remote="%s"]`, c.config.RemoteAddress)
+               c.knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{c.config.RemoteAddress: &peer})
+               if c.firstUpCall {
+                       go govpn.ScriptCall(c.config.UpPath, c.config.InterfaceName, c.config.RemoteAddress)
+                       c.firstUpCall = false
                }
                hs.Zero()
                terminator = make(chan struct{})
-               go govpn.PeerTapProcessor(peer, tap, terminator)
+               go govpn.PeerTapProcessor(peer, c.tap, terminator)
                break HandshakeCycle
        }
        if hs != nil {
@@ -103,20 +108,23 @@ HandshakeCycle:
 TransportCycle:
        for {
                select {
-               case <-termination:
+               case <-c.termination:
                        break TransportCycle
                default:
                }
                if prev == len(buf) {
-                       govpn.Printf(`[packet-timeouted remote="%s"]`, *remoteAddr)
-                       timeouted <- struct{}{}
+                       govpn.Printf(`[packet-timeouted remote="%s"]`, c.config.RemoteAddress)
+                       c.timeouted <- struct{}{}
+                       break TransportCycle
+               }
+               if err = conn.SetReadDeadline(time.Now().Add(c.config.Peer.Timeout)); err != nil {
+                       c.Error <- err
                        break TransportCycle
                }
-               conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
                n, err = conn.Read(buf[prev:])
                if err != nil {
-                       govpn.Printf(`[connection-timeouted remote="%s"]`, *remoteAddr)
-                       timeouted <- struct{}{}
+                       govpn.Printf(`[connection-timeouted remote="%s"]`, c.config.RemoteAddress)
+                       c.timeouted <- struct{}{}
                        break TransportCycle
                }
                prev += n
@@ -128,14 +136,14 @@ TransportCycle:
                if i == -1 {
                        continue
                }
-               if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) {
-                       govpn.Printf(`[packet-unauthenticated remote="%s"]`, *remoteAddr)
-                       timeouted <- struct{}{}
+               if !peer.PktProcess(buf[:i+govpn.NonceSize], c.tap, false) {
+                       govpn.Printf(`[packet-unauthenticated remote="%s"]`, c.config.RemoteAddress)
+                       c.timeouted <- struct{}{}
                        break TransportCycle
                }
                if atomic.LoadUint64(&peer.BytesIn)+atomic.LoadUint64(&peer.BytesOut) > govpn.MaxBytesPerKey {
-                       govpn.Printf(`[rehandshake-required remote="%s"]`, *remoteAddr)
-                       rehandshaking <- struct{}{}
+                       govpn.Printf(`[rehandshake-required remote="%s"]`, c.config.RemoteAddress)
+                       c.rehandshaking <- struct{}{}
                        break TransportCycle
                }
                copy(buf, buf[i+govpn.NonceSize:prev])
@@ -146,5 +154,7 @@ TransportCycle:
                terminator <- struct{}{}
        }
        peer.Zero()
-       conn.Close()
+       if err = conn.Close(); err != nil {
+               c.Error <- err
+       }
 }
similarity index 51%
rename from src/cypherpunks.ru/govpn/cmd/govpn-client/udp.go
rename to src/cypherpunks.ru/govpn/client/udp.go
index 96dbfba418822b9833c40ffd7154070a11c3c8b6..541d6318c758e8a6a6adf6204da71700318a4f1e 100644 (file)
@@ -16,10 +16,10 @@ 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 main
+package client
 
 import (
-       "log"
+       "fmt"
        "net"
        "sync/atomic"
        "time"
@@ -27,36 +27,42 @@ import (
        "cypherpunks.ru/govpn"
 )
 
-func startUDP(timeouted, rehandshaking, termination chan struct{}) {
-       remote, err := net.ResolveUDPAddr("udp", *remoteAddr)
+func (c *Client) startUDP() {
+       remote, err := net.ResolveUDPAddr("udp", c.config.RemoteAddress)
        if err != nil {
-               log.Fatalln("Can not resolve remote address:", err)
+               c.Error <- fmt.Errorf("Can not resolve remote address: %s", err)
+               return
        }
        conn, err := net.DialUDP("udp", nil, remote)
        if err != nil {
-               log.Fatalln("Can not listen on UDP:", err)
+               c.Error <- fmt.Errorf("Can not connect remote address: %s", err)
+               return
        }
-       govpn.Printf(`[connected remote="%s"]`, *remoteAddr)
+       govpn.Printf(`[connected remote="%s"]`, c.config.RemoteAddress)
 
-       hs := govpn.HandshakeStart(*remoteAddr, conn, conf)
-       buf := make([]byte, *mtu*2)
+       hs := govpn.HandshakeStart(c.config.RemoteAddress, conn, c.config.Peer)
+       buf := make([]byte, c.config.MTU*2)
        var n int
        var timeouts int
        var peer *govpn.Peer
        var terminator chan struct{}
+       timeout := int(c.config.Peer.Timeout.Seconds())
 MainCycle:
        for {
                select {
-               case <-termination:
+               case <-c.termination:
                        break MainCycle
                default:
                }
 
-               conn.SetReadDeadline(time.Now().Add(time.Second))
+               if err = conn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
+                       c.Error <- err
+                       break MainCycle
+               }
                n, err = conn.Read(buf)
                if timeouts == timeout {
-                       govpn.Printf(`[connection-timeouted remote="%s"]`, *remoteAddr)
-                       timeouted <- struct{}{}
+                       govpn.Printf(`[connection-timeouted remote="%s"]`, c.config.RemoteAddress)
+                       c.timeouted <- struct{}{}
                        break
                }
                if err != nil {
@@ -64,21 +70,21 @@ MainCycle:
                        continue
                }
                if peer != nil {
-                       if peer.PktProcess(buf[:n], tap, true) {
+                       if peer.PktProcess(buf[:n], c.tap, true) {
                                timeouts = 0
                        } else {
-                               govpn.Printf(`[packet-unauthenticated remote="%s"]`, *remoteAddr)
+                               govpn.Printf(`[packet-unauthenticated remote="%s"]`, c.config.RemoteAddress)
                                timeouts++
                        }
                        if atomic.LoadUint64(&peer.BytesIn)+atomic.LoadUint64(&peer.BytesOut) > govpn.MaxBytesPerKey {
-                               govpn.Printf(`[rehandshake-required remote="%s"]`, *remoteAddr)
-                               rehandshaking <- struct{}{}
+                               govpn.Printf(`[rehandshake-required remote="%s"]`, c.config.RemoteAddress)
+                               c.rehandshaking <- struct{}{}
                                break MainCycle
                        }
                        continue
                }
-               if idsCache.Find(buf[:n]) == nil {
-                       govpn.Printf(`[identity-invalid remote="%s"]`, *remoteAddr)
+               if c.idsCache.Find(buf[:n]) == nil {
+                       govpn.Printf(`[identity-invalid remote="%s"]`, c.config.RemoteAddress)
                        continue
                }
                timeouts = 0
@@ -86,15 +92,15 @@ MainCycle:
                if peer == nil {
                        continue
                }
-               govpn.Printf(`[handshake-completed remote="%s"]`, *remoteAddr)
-               knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer})
-               if firstUpCall {
-                       go govpn.ScriptCall(*upPath, *ifaceName, *remoteAddr)
-                       firstUpCall = false
+               govpn.Printf(`[handshake-completed remote="%s"]`, c.config.RemoteAddress)
+               c.knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{c.config.RemoteAddress: &peer})
+               if c.firstUpCall {
+                       go govpn.ScriptCall(c.config.UpPath, c.config.InterfaceName, c.config.RemoteAddress)
+                       c.firstUpCall = false
                }
                hs.Zero()
                terminator = make(chan struct{})
-               go govpn.PeerTapProcessor(peer, tap, terminator)
+               go govpn.PeerTapProcessor(peer, c.tap, terminator)
        }
        if terminator != nil {
                terminator <- struct{}{}
@@ -102,5 +108,7 @@ MainCycle:
        if hs != nil {
                hs.Zero()
        }
-       conn.Close()
+       if err = conn.Close(); err != nil {
+               c.Error <- err
+       }
 }
index 36ff72a113e6ac0db3e809e90af6954fb57f9f6c..df426ba8820c45bac23d84d83a21e814cf6e5540 100644 (file)
@@ -23,46 +23,41 @@ import (
        "flag"
        "fmt"
        "log"
-       "net"
        "os"
        "os/signal"
        "time"
 
        "cypherpunks.ru/govpn"
-)
-
-var (
-       remoteAddr  = flag.String("remote", "", "Remote server address")
-       proto       = flag.String("proto", "udp", "Protocol to use: udp or tcp")
-       ifaceName   = flag.String("iface", "tap0", "TUN/TAP network interface")
-       verifierRaw = flag.String("verifier", "", "Verifier")
-       keyPath     = flag.String("key", "", "Path to passphrase file")
-       upPath      = flag.String("up", "", "Path to up-script")
-       downPath    = flag.String("down", "", "Path to down-script")
-       stats       = flag.String("stats", "", "Enable stats retrieving on host:port")
-       proxyAddr   = flag.String("proxy", "", "Use HTTP proxy on host:port")
-       proxyAuth   = flag.String("proxy-auth", "", "user:password Basic proxy auth")
-       mtu         = flag.Int("mtu", govpn.MTUDefault, "MTU of TUN/TAP interface")
-       timeoutP    = flag.Int("timeout", 60, "Timeout seconds")
-       timeSync    = flag.Int("timesync", 0, "Time synchronization requirement")
-       noreconnect = flag.Bool("noreconnect", false, "Disable reconnection after timeout")
-       noisy       = flag.Bool("noise", false, "Enable noise appending")
-       encless     = flag.Bool("encless", false, "Encryptionless mode")
-       cpr         = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
-       egdPath     = flag.String("egd", "", "Optional path to EGD socket")
-       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")
-
-       conf        *govpn.PeerConf
-       tap         *govpn.TAP
-       timeout     int
-       firstUpCall bool = true
-       knownPeers  govpn.KnownPeers
-       idsCache    *govpn.MACCache
+       "cypherpunks.ru/govpn/client"
 )
 
 func main() {
+       var (
+               remoteAddr  = flag.String("remote", "", "Remote server address")
+               proto       = flag.String("proto", "udp", "Protocol to use: udp or tcp")
+               ifaceName   = flag.String("iface", "tap0", "TUN/TAP network interface")
+               verifierRaw = flag.String("verifier", "", "Verifier")
+               keyPath     = flag.String("key", "", "Path to passphrase file")
+               upPath      = flag.String("up", "", "Path to up-script")
+               downPath    = flag.String("down", "", "Path to down-script")
+               stats       = flag.String("stats", "", "Enable stats retrieving on host:port")
+               proxyAddr   = flag.String("proxy", "", "Use HTTP proxy on host:port")
+               proxyAuth   = flag.String("proxy-auth", "", "user:password Basic proxy auth")
+               mtu         = flag.Int("mtu", govpn.MTUDefault, "MTU of TUN/TAP interface")
+               timeoutP    = flag.Int("timeout", 60, "Timeout seconds")
+               timeSync    = flag.Int("timesync", 0, "Time synchronization requirement")
+               noreconnect = flag.Bool("noreconnect", false, "Disable reconnection after timeout")
+               noisy       = flag.Bool("noise", false, "Enable noise appending")
+               encless     = flag.Bool("encless", false, "Encryptionless mode")
+               cpr         = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
+               egdPath     = flag.String("egd", "", "Optional path to EGD socket")
+               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")
+               protocol client.Protocol
+               err error
+       )
+
        flag.Parse()
        if *warranty {
                fmt.Println(govpn.Warranty)
@@ -72,72 +67,78 @@ func main() {
                fmt.Println(govpn.VersionGet())
                return
        }
-       timeout = *timeoutP
-       var err error
        log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
 
-       if *mtu > govpn.MTUMax {
-               log.Fatalln("Maximum allowable MTU is", govpn.MTUMax)
-       }
        if *egdPath != "" {
                log.Println("Using", *egdPath, "EGD")
                govpn.EGDInit(*egdPath)
        }
 
-       if *proxyAddr != "" {
-               *proto = "tcp"
-       }
-       if !(*proto == "udp" || *proto == "tcp") {
+       switch *proto {
+       case "udp":
+               protocol = client.ProtocolUDP
+       case "tcp":
+               protocol = client.ProtocolTCP
+       default:
                log.Fatalln("Unknown protocol specified")
        }
+
+       if *proxyAddr != "" && protocol == client.ProtocolUDP {
+               log.Println("Proxy is supported for TCP only, switch")
+               protocol = client.ProtocolTCP
+       }
+
        if *verifierRaw == "" {
-               log.Fatalln("No verifier specified")
+               log.Fatalln("-verifier is required")
        }
        verifier, err := govpn.VerifierFromString(*verifierRaw)
        if err != nil {
-               log.Fatalln(err)
+               log.Fatalln("Invalid -verifier:", err)
        }
        key, err := govpn.KeyRead(*keyPath)
        if err != nil {
-               log.Fatalln("Unable to read the key", err)
+               log.Fatalln("Invalid -key:", err)
        }
        priv := verifier.PasswordApply(key)
        if *encless {
-               if *proto != "tcp" {
+               if protocol != client.ProtocolTCP {
                        log.Fatalln("Currently encryptionless mode works only with TCP")
                }
-               *noisy = true
+               if !*noisy {
+                       log.Println("-encless is on, force -noisy mode")
+                       *noisy = true
+               }
        }
-       conf = &govpn.PeerConf{
-               Id:       verifier.Id,
-               Iface:    *ifaceName,
-               MTU:      *mtu,
-               Timeout:  time.Second * time.Duration(timeout),
-               TimeSync: *timeSync,
-               Noise:    *noisy,
-               CPR:      *cpr,
-               Encless:  *encless,
-               Verifier: verifier,
-               DSAPriv:  priv,
+       conf := client.Configuration{
+               PrivateKey: priv,
+               Peer: &govpn.PeerConf{
+                       Id:       verifier.Id,
+                       Iface:    *ifaceName,
+                       MTU:      *mtu,
+                       Timeout:  time.Second * time.Duration(*timeoutP),
+                       TimeSync: *timeSync,
+                       Noise:    *noisy,
+                       CPR:      *cpr,
+                       Encless:  *encless,
+                       Verifier: verifier,
+                       DSAPriv:  priv,
+               },
+               Protocol: protocol,
+               InterfaceName: *ifaceName,
+               ProxyAddress: *proxyAddr,
+               ProxyAuthentication: *proxyAuth,
+               RemoteAddress: *remoteAddr,
+               UpPath: *upPath,
+               DownPath: *downPath,
+               StatsAddress: *stats,
+               NoReconnect: *noreconnect,
+               MTU: *mtu,
        }
-       idsCache = govpn.NewMACCache()
-       confs := map[govpn.PeerId]*govpn.PeerConf{*verifier.Id: conf}
-       idsCache.Update(&confs)
-       log.Println(govpn.VersionGet())
-
-       tap, err = govpn.TAPListen(*ifaceName, *mtu)
-       if err != nil {
-               log.Fatalln("Can not listen on TUN/TAP interface:", err)
+       if err = conf.Validate(); err != nil {
+               log.Fatalln("Invalid settings:", err)
        }
 
-       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)
-       }
+       log.Println(govpn.VersionGet())
 
        if *syslog {
                govpn.SyslogEnable()
@@ -145,38 +146,11 @@ func main() {
 
        termSignal := make(chan os.Signal, 1)
        signal.Notify(termSignal, os.Interrupt, os.Kill)
-
-MainCycle:
-       for {
-               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)
-                       }
-               }
-               select {
-               case <-termSignal:
-                       govpn.BothPrintf(`[finish remote="%s"]`, *remoteAddr)
-                       termination <- struct{}{}
-                       break MainCycle
-               case <-timeouted:
-                       if *noreconnect {
-                               break MainCycle
-                       }
-                       govpn.BothPrintf(`[sleep seconds="%d"]`, timeout)
-                       time.Sleep(time.Second * time.Duration(timeout))
-               case <-rehandshaking:
-               }
-               close(timeouted)
-               close(rehandshaking)
-               close(termination)
+       c := client.NewClient(conf, verifier, termSignal)
+       go c.MainCycle()
+       if err := <-c.Error; err != nil {
+               log.Fatalln(err)
+       } else {
+               log.Println("Closed VPN tunnel")
        }
-       govpn.ScriptCall(*downPath, *ifaceName, *remoteAddr)
 }