From f353bc9c0734903785ae8d5a6edd7aa338bdbfe8 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sun, 23 Aug 2015 12:56:00 +0300 Subject: [PATCH] Ability to use HTTP proxies for accessing server Signed-off-by: Sergey Matveev --- doc/client.texi | 13 +++- doc/overview.texi | 1 + doc/proxy.texi | 16 +++++ doc/server.texi | 9 +++ doc/todo.texi | 1 - doc/user.texi | 2 + src/govpn/cmd/govpn-client/main.go | 8 ++- src/govpn/cmd/govpn-client/proxy.go | 59 ++++++++++++++++ src/govpn/cmd/govpn-client/tcp.go | 80 +++++++++++----------- src/govpn/cmd/govpn-server/main.go | 12 ++-- src/govpn/cmd/govpn-server/proxy.go | 50 ++++++++++++++ src/govpn/cmd/govpn-server/tcp.go | 100 +++++++++++++++------------- src/govpn/cmd/govpn-server/udp.go | 6 +- 13 files changed, 261 insertions(+), 96 deletions(-) create mode 100644 doc/proxy.texi create mode 100644 src/govpn/cmd/govpn-client/proxy.go create mode 100644 src/govpn/cmd/govpn-server/proxy.go diff --git a/doc/client.texi b/doc/client.texi index d224fff..ec816c4 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -1,11 +1,22 @@ @node Client part @section Client part -Except for common @code{-proto}, @code{-mtu}, @code{-stats}, @code{-egd} +Except for common @code{-mtu}, @code{-stats}, @code{-egd} options client has the following ones: @table @code +@item -proto +@ref{Network transport} to use. Can be either @emph{udp} or @emph{tcp}. + +@item -proxy +Use specified @emph{host:port} @ref{Proxy} server for accessing remote +server. + +@item -proxy-auth +Optional @emph{user:password} for HTTP Basic authorization on proxy +server. + @item -remote Address (@code{host:port} format) of remote server we need to connect to. diff --git a/doc/overview.texi b/doc/overview.texi index 502422a..9f7846e 100644 --- a/doc/overview.texi +++ b/doc/overview.texi @@ -46,6 +46,7 @@ Copylefted free software: licensed under @item Works with @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP} network interfaces on top of either UDP or TCP entirely. +@item Ability to use HTTP proxies to access TCP server. @item @url{https://www.gnu.org/, GNU}/Linux and @url{http://www.freebsd.org/, FreeBSD} support. diff --git a/doc/proxy.texi b/doc/proxy.texi new file mode 100644 index 0000000..e5f5b80 --- /dev/null +++ b/doc/proxy.texi @@ -0,0 +1,16 @@ +@node Proxy +@section Proxy + +You can proxy your requests through HTTP using CONNECT method. This can +help if you are only allowed to access outside world through HTTP proxy +server. + +Server has @emph{-proxy} option allowing to listen on specified port and +accept HTTP request. All of them will be treated as a CONNECT method +switching to raw TCP mode. You can make POST request and server will +anyway switch to raw TCP mode. You are not forced to use this option: +any external HTTP proxy server can be used. + +Client has @emph{-proxy} option forcing it to connect to proxy and send +CONNECT method. Optionally it can be authenticated on it using +@emph{-proxy-auth} HTTP Basic method. diff --git a/doc/server.texi b/doc/server.texi index 989a8a8..e86d86a 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -5,10 +5,19 @@ Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server has the following ones: @table @code + +@item -proto +@ref{Network transport} to use. Can be @emph{udp}, @emph{tcp} or @emph{all}. + @item -bind Address (@code{host:port} format) we must bind to. + @item -peers Path to the directory containing peers information, database. + +@item -proxy +Start trivial HTTP @ref{Proxy} server on specified @emph{host:port}. + @end table Peers directory must contain subdirectories with the names of client's diff --git a/doc/todo.texi b/doc/todo.texi index 93b95f1..f8064cf 100644 --- a/doc/todo.texi +++ b/doc/todo.texi @@ -4,7 +4,6 @@ @itemize @item Ability to tunnel only specified TCP connections, without full featured VPN solution. Similar to ssh -R. -@item Ability to work over HTTP, WebSockets and something similar. @item Ability to noise handshake packets sizes. @item Increase performance. @item Randomize ports usage. diff --git a/doc/user.texi b/doc/user.texi index 8959c74..4139822 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -19,6 +19,7 @@ with @emph{Go 1.5} gives 435 Mbps TCP throughput. * PAKE:: Password Authenticated Key Agreement * Timeout:: * Network transport:: +* Proxy:: * MTU:: Maximum Transmission Unit * Stats:: * Noise:: @@ -34,6 +35,7 @@ with @emph{Go 1.5} gives 435 Mbps TCP throughput. @include pake.texi @include timeout.texi @include netproto.texi +@include proxy.texi @include mtu.texi @include stats.texi @include noise.texi diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index 00d8f6a..7f32b6c 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -40,6 +40,8 @@ var ( 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", 1452, "MTU for outgoing packets") timeoutP = flag.Int("timeout", 60, "Timeout seconds") noisy = flag.Bool("noise", false, "Enable noise appending") @@ -83,7 +85,11 @@ func main() { case "udp": conn, sink, ready = startUDP() case "tcp": - conn, sink, ready = startTCP() + if *proxyAddr != "" { + conn, sink, ready = proxyTCP() + } else { + conn, sink, ready = startTCP() + } default: log.Fatalln("Unknown protocol specified") } diff --git a/src/govpn/cmd/govpn-client/proxy.go b/src/govpn/cmd/govpn-client/proxy.go new file mode 100644 index 0000000..678a5ae --- /dev/null +++ b/src/govpn/cmd/govpn-client/proxy.go @@ -0,0 +1,59 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 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 ( + "bufio" + "encoding/base64" + "io" + "log" + "net" + "net/http" +) + +func proxyTCP() (io.Writer, chan []byte, chan struct{}) { + proxyAddr, err := net.ResolveTCPAddr("tcp", *proxyAddr) + if err != nil { + log.Fatalln("Can not resolve proxy address:", err) + } + conn, err := net.DialTCP("tcp", nil, proxyAddr) + if err != nil { + log.Fatalln("Can not connect to proxy:", err) + } + req := "CONNECT " + *remoteAddr + " HTTP/1.1\n" + req += "Host: " + *remoteAddr + "\n" + if *proxyAuth != "" { + req += "Proxy-Authorization: Basic " + req += base64.StdEncoding.EncodeToString([]byte(*proxyAuth)) + "\n" + } + req += "\n" + conn.Write([]byte(req)) + resp, err := http.ReadResponse( + bufio.NewReader(conn), + &http.Request{Method: "CONNECT"}, + ) + 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 +} diff --git a/src/govpn/cmd/govpn-client/tcp.go b/src/govpn/cmd/govpn-client/tcp.go index aa868da..373789a 100644 --- a/src/govpn/cmd/govpn-client/tcp.go +++ b/src/govpn/cmd/govpn-client/tcp.go @@ -50,46 +50,50 @@ func startTCP() (io.Writer, chan []byte, chan struct{}) { } sink := make(chan []byte) ready := make(chan struct{}) - go func() { - var err error - var n int - var sizeNbuf int - sizeBuf := make([]byte, 2) - var sizeNeed uint16 - var bufN uint16 - buf := make([]byte, govpn.MTU) - for { - <-ready + go handleTCP(c, sink, ready) + go func() { ready <- struct{}{} }() + return conn, sink, ready +} + +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 + buf := make([]byte, govpn.MTU) + for { + <-ready + if sizeNbuf != 2 { + n, err = conn.Read(sizeBuf[sizeNbuf:2]) + if err != nil { + break + } + sizeNbuf += n if sizeNbuf != 2 { - n, err = c.Read(sizeBuf[sizeNbuf:2]) - if err != nil { - break - } - sizeNbuf += n - if sizeNbuf == 2 { - sizeNeed = binary.BigEndian.Uint16(sizeBuf) - if sizeNeed > uint16(govpn.MTU)-2 { - log.Println("Invalid TCP size, skipping") - sizeNbuf = 0 - sink <- nil - continue - } - bufN = 0 - } + sink <- nil + continue } - ReadMore: - if sizeNeed != bufN { - n, err = c.Read(buf[bufN:sizeNeed]) - if err != nil { - break - } - bufN += uint16(n) - goto ReadMore + sizeNeed = binary.BigEndian.Uint16(sizeBuf) + if int(sizeNeed) > govpn.MTU-2 { + log.Println("Invalid TCP size, skipping") + sizeNbuf = 0 + sink <- nil + continue } - sizeNbuf = 0 - sink <- buf[:sizeNeed] + bufN = 0 } - }() - go func() { ready <- struct{}{} }() - return conn, sink, ready + ReadMore: + if sizeNeed != bufN { + n, err = conn.Read(buf[bufN:sizeNeed]) + if err != nil { + break + } + bufN += uint16(n) + goto ReadMore + } + sizeNbuf = 0 + sink <- buf[:sizeNeed] + } } diff --git a/src/govpn/cmd/govpn-server/main.go b/src/govpn/cmd/govpn-server/main.go index 664e04d..37135b9 100644 --- a/src/govpn/cmd/govpn-server/main.go +++ b/src/govpn/cmd/govpn-server/main.go @@ -38,6 +38,7 @@ var ( proto = flag.String("proto", "udp", "Protocol to use: udp, tcp or all") peersPath = flag.String("peers", "peers", "Path to peers keys directory") stats = flag.String("stats", "", "Enable stats retrieving on host:port") + proxy = flag.String("proxy", "", "Enable HTTP proxy on host:port") mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") egdPath = flag.String("egd", "", "Optional path to EGD socket") ) @@ -101,12 +102,12 @@ func main() { sink := make(chan Pkt) switch *proto { case "udp": - startUDP(&sink) + startUDP(sink) case "tcp": - startTCP(&sink) + startTCP(sink) case "all": - startUDP(&sink) - startTCP(&sink) + startUDP(sink) + startTCP(sink) default: log.Fatalln("Unknown protocol specified") } @@ -142,6 +143,9 @@ func main() { } go govpn.StatsProcessor(statsPort, &knownPeers) } + if *proxy != "" { + go proxyStart(sink) + } log.Println("Server started") MainCycle: diff --git a/src/govpn/cmd/govpn-server/proxy.go b/src/govpn/cmd/govpn-server/proxy.go new file mode 100644 index 0000000..f1e8419 --- /dev/null +++ b/src/govpn/cmd/govpn-server/proxy.go @@ -0,0 +1,50 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 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/http" +) + +type proxyHandler struct { + sink chan Pkt +} + +func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + conn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + log.Println("Hijacking failed:", err.Error()) + return + } + conn.Write([]byte("HTTP/1.0 200 OK\n\n")) + ready := make(chan struct{}, 1) + go handleTCP(conn, p.sink, ready) + ready <- struct{}{} + +} + +func proxyStart(sink chan Pkt) { + log.Println("HTTP proxy listening on:", *proxy) + s := &http.Server{ + Addr: *proxy, + Handler: proxyHandler{sink}, + } + log.Println("HTTP proxy result:", s.ListenAndServe()) +} diff --git a/src/govpn/cmd/govpn-server/tcp.go b/src/govpn/cmd/govpn-server/tcp.go index c7b1788..42467d6 100644 --- a/src/govpn/cmd/govpn-server/tcp.go +++ b/src/govpn/cmd/govpn-server/tcp.go @@ -27,7 +27,7 @@ import ( ) type TCPSender struct { - conn *net.TCPConn + conn net.Conn } func (c TCPSender) Write(data []byte) (int, error) { @@ -36,7 +36,7 @@ func (c TCPSender) Write(data []byte) (int, error) { return c.conn.Write(append(size, data...)) } -func startTCP(sink *chan Pkt) { +func startTCP(sink chan Pkt) { bind, err := net.ResolveTCPAddr("tcp", *bindAddr) if err != nil { log.Fatalln("Can not resolve bind address:", err) @@ -50,53 +50,57 @@ func startTCP(sink *chan Pkt) { for { conn, _ := listener.AcceptTCP() ready := make(chan struct{}, 1) - go func(conn *net.TCPConn, ready chan struct{}) { - 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) - for { - <-ready - if sizeNbuf != 2 { - n, err = conn.Read(sizeBuf[sizeNbuf:2]) - if err != nil { - break - } - sizeNbuf += n - if sizeNbuf == 2 { - sizeNeed = binary.BigEndian.Uint16(sizeBuf) - if sizeNeed > uint16(govpn.MTU)-2 { - log.Println("Invalid TCP size, skipping") - sizeNbuf = 0 - *sink <- Pkt{ready: ready} - continue - } - bufN = 0 - } - } - ReadMore: - if sizeNeed != bufN { - n, err = conn.Read(buf[bufN:sizeNeed]) - if err != nil { - break - } - bufN += uint16(n) - goto ReadMore - } - sizeNbuf = 0 - *sink <- Pkt{ - addr, - TCPSender{conn}, - buf[:sizeNeed], - ready, - } - } - }(conn, ready) + go handleTCP(conn, sink, ready) ready <- struct{}{} } }() } + +func handleTCP(conn net.Conn, sink chan Pkt, ready chan struct{}) { + 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) + for { + <-ready + if sizeNbuf != 2 { + n, err = conn.Read(sizeBuf[sizeNbuf:2]) + if err != nil { + break + } + sizeNbuf += n + if sizeNbuf != 2 { + sink <- Pkt{ready: ready} + continue + } + sizeNeed = binary.BigEndian.Uint16(sizeBuf) + if int(sizeNeed) > govpn.MTU-2 { + log.Println("Invalid TCP size, skipping") + sizeNbuf = 0 + sink <- Pkt{ready: ready} + continue + } + bufN = 0 + } + ReadMore: + if sizeNeed != bufN { + n, err = conn.Read(buf[bufN:sizeNeed]) + if err != nil { + break + } + bufN += uint16(n) + goto ReadMore + } + sizeNbuf = 0 + sink <- Pkt{ + addr, + TCPSender{conn}, + buf[:sizeNeed], + ready, + } + } +} diff --git a/src/govpn/cmd/govpn-server/udp.go b/src/govpn/cmd/govpn-server/udp.go index 26b28a8..c2d6e96 100644 --- a/src/govpn/cmd/govpn-server/udp.go +++ b/src/govpn/cmd/govpn-server/udp.go @@ -35,7 +35,7 @@ func (c UDPSender) Write(data []byte) (int, error) { return c.conn.WriteToUDP(data, c.addr) } -func startUDP(sink *chan Pkt) { +func startUDP(sink chan Pkt) { bind, err := net.ResolveUDPAddr("udp", *bindAddr) ready := make(chan struct{}) if err != nil { @@ -56,10 +56,10 @@ func startUDP(sink *chan Pkt) { lconn.SetReadDeadline(time.Now().Add(time.Second)) n, raddr, err = lconn.ReadFromUDP(buf) if err != nil { - *sink <- Pkt{ready: ready} + sink <- Pkt{ready: ready} continue } - *sink <- Pkt{ + sink <- Pkt{ raddr.String(), UDPSender{lconn, raddr}, buf[:n], -- 2.44.0