Commit ff6d3e14 authored by rui.zheng's avatar rui.zheng

add gost library

parent b3461ea5
MIT License
Copyright (c) 2016 ginuerzh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
package gost
import (
"crypto/tls"
"errors"
"github.com/golang/glog"
"golang.org/x/net/http2"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
// Proxy chain holds a list of proxy nodes
type ProxyChain struct {
nodes []ProxyNode
lastNode *ProxyNode
http2NodeIndex int
http2Enabled bool
http2Client *http.Client
}
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
chain := &ProxyChain{nodes: nodes, http2NodeIndex: -1}
return chain
}
func (c *ProxyChain) AddProxyNode(node ...ProxyNode) {
c.nodes = append(c.nodes, node...)
}
// Initialize proxy nodes, mainly check for http2 feature.
// Should be called immediately when proxy nodes are ready.
//
// NOTE: http2 will not be enabled if not called.
func (c *ProxyChain) Init() {
length := len(c.nodes)
if length == 0 {
return
}
c.lastNode = &c.nodes[length-1]
// http2 restrict: http2 will be enabled when at least one http2 proxy node present
for i, node := range c.nodes {
if node.Transport == "http2" {
glog.V(LINFO).Infoln("http2 enabled")
cfg := &tls.Config{
InsecureSkipVerify: node.insecureSkipVerify(),
ServerName: node.serverName,
}
c.initHttp2Client(node.Addr, cfg, c.nodes[:i]...)
c.http2NodeIndex = i
break // shortest chain for http2
}
}
}
func (c *ProxyChain) initHttp2Client(addr string, config *tls.Config, nodes ...ProxyNode) {
tr := http2.Transport{
TLSClientConfig: config,
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
// replace the default dialer with our proxy chain.
conn, err := c.dialWithNodes(addr, nodes...)
if err != nil {
return conn, err
}
return tls.Client(conn, cfg), nil
},
}
c.http2Client = &http.Client{Transport: &tr}
c.http2Enabled = true
}
func (c *ProxyChain) Http2Enabled() bool {
return c.http2Enabled
}
// Connect to addr through proxy chain
func (c *ProxyChain) Dial(addr string) (net.Conn, error) {
if !strings.Contains(addr, ":") {
addr += ":80"
}
return c.dialWithNodes(addr, c.nodes...)
}
func (c *ProxyChain) dialWithNodes(addr string, nodes ...ProxyNode) (conn net.Conn, err error) {
if len(nodes) == 0 {
return net.DialTimeout("tcp", addr, DialTimeout)
}
var pc *ProxyConn
if c.Http2Enabled() {
nodes = nodes[c.http2NodeIndex+1:]
if len(nodes) == 0 {
return c.http2Connect("http", addr)
}
}
pc, err = c.travelNodes(nodes...)
if err != nil {
return
}
if err = pc.Connect(addr); err != nil {
pc.Close()
return
}
return pc, nil
}
func (c *ProxyChain) travelNodes(nodes ...ProxyNode) (conn *ProxyConn, err error) {
defer func() {
if err != nil && conn != nil {
conn.Close()
conn = nil
}
}()
var cc net.Conn
node := nodes[0]
if c.Http2Enabled() {
cc, err = c.http2Connect("http", node.Addr)
} else {
cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout)
}
if err != nil {
return
}
setKeepAlive(cc, KeepAliveTime)
pc := NewProxyConn(cc, node)
if err = pc.Handshake(); err != nil {
return
}
conn = pc
for _, node := range nodes[1:] {
if err = conn.Connect(node.Addr); err != nil {
return
}
pc := NewProxyConn(conn, node)
if err = pc.Handshake(); err != nil {
return
}
conn = pc
}
return
}
func (c *ProxyChain) http2Connect(protocol, addr string) (net.Conn, error) {
if !c.Http2Enabled() {
return nil, errors.New("http2 not enabled")
}
http2Node := c.nodes[c.http2NodeIndex]
pr, pw := io.Pipe()
req := http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: "https", Host: http2Node.Addr},
Header: make(http.Header),
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
Body: ioutil.NopCloser(pr),
Host: http2Node.Addr,
ContentLength: -1,
}
req.Header.Set("gost-target", addr)
if protocol != "" {
req.Header.Set("gost-protocol", protocol)
}
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(&req, false)
glog.Infoln(string(dump))
}
resp, err := c.http2Client.Do(&req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, errors.New(resp.Status)
}
conn := &Http2ClientConn{r: resp.Body, w: pw}
conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr)
return conn, nil
}
This diff is collapsed.
package gost
import (
"crypto/tls"
"errors"
"github.com/golang/glog"
"net"
"time"
)
const (
LFATAL = iota
LERROR
LWARNING
LINFO
LDEBUG
LVDEBUG // verbose debug for http2
)
var (
DialTimeout = 30 * time.Second
KeepAliveTime = 180 * time.Second
)
var (
DefaultCertFile = "cert.pem"
DefaultKeyFile = "key.pem"
// This is the default cert and key data for convenience, providing your own cert is recommended.
DefaultRawCert = `-----BEGIN CERTIFICATE-----
MIIC5jCCAdCgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
bzAeFw0xNDAzMTcwNjIwNTFaFw0xNTAzMTcwNjIwNTFaMBIxEDAOBgNVBAoTB0Fj
bWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccNO1xmd4lWSf
d/0/QS3E93cYIWHw831i/IKxigdRD/XMZonLdEHywW6lOiXazaP8e6CqPGSmnl0x
5k/3dvGCMj2JCVxM6+z7NpL+AiwvXmvkj/TOciCgwqssCwYS2CiVwjfazRjx1ZUJ
VDC5qiyRsfktQ2fVHrpnJGVSRagmiQgwGWBilVG9B8QvRtpQKN/GQGq17oIQm8aK
kOdPt93g93ojMIg7YJpgDgOirvVz/hDn7YD4ryrtPos9CMafFkJprymKpRHyvz7P
8a3+OkuPjFjPnwOHQ5u1U3+8vC44vfb1ExWzDLoT8Xp8Gndx39k0f7MVOol3GnYu
MN/dvNUdAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIAoDATBgNVHSUEDDAKBggrBgEF
BQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDALBgkqhkiG
9w0BAQUDggEBAIG8CJqvTIgJnNOK+i5/IUc/3yF/mSCWuG8qP+Fmo2t6T0PVOtc0
8wiWH5iWtCAhjn0MRY9l/hIjWm6gUZGHCGuEgsOPpJDYGoNLjH9Xwokm4y3LFNRK
UBrrrDbKRNibApBHCapPf6gC5sXcjOwx7P2/kiHDgY7YH47jfcRhtAPNsM4gjsEO
RmwENY+hRUFHIRfQTyalqND+x6PWhRo3K6hpHs4DQEYPq4P2kFPqUqSBymH+Ny5/
BcQ3wdMNmC6Bm/oiL1QV0M+/InOsAgQk/EDd0kmoU1ZT2lYHQduGmP099bOlHNpS
uqO3vXF3q8SPPr/A9TqSs7BKkBQbe0+cdsA=
-----END CERTIFICATE-----`
DefaultRawKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA3HDTtcZneJVkn3f9P0EtxPd3GCFh8PN9YvyCsYoHUQ/1zGaJ
y3RB8sFupTol2s2j/Hugqjxkpp5dMeZP93bxgjI9iQlcTOvs+zaS/gIsL15r5I/0
znIgoMKrLAsGEtgolcI32s0Y8dWVCVQwuaoskbH5LUNn1R66ZyRlUkWoJokIMBlg
YpVRvQfEL0baUCjfxkBqte6CEJvGipDnT7fd4Pd6IzCIO2CaYA4Doq71c/4Q5+2A
+K8q7T6LPQjGnxZCaa8piqUR8r8+z/Gt/jpLj4xYz58Dh0ObtVN/vLwuOL329RMV
swy6E/F6fBp3cd/ZNH+zFTqJdxp2LjDf3bzVHQIDAQABAoIBAHal26147nQ+pHwY
jxwers3XDCjWvup7g79lfcqlKi79UiUEA6KYHm7UogMYewt7p4nb2KwH+XycvDiB
aAUf5flXpTs+6IkWauUDiLZi4PlV7uiEexUq5FjirlL0U/6MjbudX4bK4WQ4uxDc
WaV07Kw2iJFOOHLDKT0en9JaX5jtJNc4ZnE9efFoQ5jfypPWtRw65G1rULEg6nvc
GDh+1ce+4foCkpLRC9c24xAwJONZG6x3UqrSS9qfAsb73nWRQrTfUcO3nhoN8VvL
kL9skn1+S06NyUN0KoEtyRBp+RcpXSsBWAo6qZmo/WqhB/gjzWrxVwn20+yJSm35
ZsMc6QECgYEA8GS+Mp9xfB2szWHz6YTOO1Uu4lHM1ccZMwS1G+dL0KO3uGAiPdvp
woVot6v6w88t7onXsLo5pgz7SYug0CpkF3K/MRd1Ar4lH7PK7IBQ6rFr9ppVxDbx
AEWRswUoPbKCr7W6HU8LbQHDavsDlEIwc6+DiwnL4BzlKjb7RpgQEz0CgYEA6sB5
uHvx3Y5FDcGk1n73leQSAcq14l3ZLNpjrs8msoREDil/j5WmuSN58/7PGMiMgHEi
1vLm3H796JmvGr9OBvspOjHyk07ui2/We/j9Hoxm1VWhyi8HkLNDj70HKalTTFMz
RHO4O+0xCva+h9mKZrRMVktXr2jjdFn/0MYIZ2ECgYAIIsC1IeRLWQ3CHbCNlKsO
IwHlMvOFwKk/qsceXKOaOhA7szU1dr3gkXdL0Aw6mEZrrkqYdpUA46uVf54/rU+Z
445I8QxKvXiwK/uQKX+TkdGflPWWIG3jnnch4ejMvb/ihnn4B/bRB6A/fKNQXzUY
lTYUfI5j1VaEKTwz1W2l2QKBgByFCcSp+jZqhGUpc3dDsZyaOr3Q/Mvlju7uEVI5
hIAHpaT60a6GBd1UPAqymEJwivFHzW3D0NxU6VAK68UaHMaoWNfjHY9b9YsnKS2i
kE3XzN56Ks+/avHfdYPO+UHMenw5V28nh+hv5pdoZrlmanQTz3pkaOC8o3WNQZEB
nh/BAoGBAMY5z2f1pmMhrvtPDSlEVjgjELbaInxFaxPLR4Pdyzn83gtIIU14+R8X
2LPs6PPwrNjWnIgrUSVXncIFL3pa45B+Mx1pYCpOAB1+nCZjIBQmpeo4Y0dwA/XH
85EthKPvoszm+OPbyI16OcePV5ocX7lupRYuAo0pek7bomhmHWHz
-----END RSA PRIVATE KEY-----`
)
func setKeepAlive(conn net.Conn, d time.Duration) error {
c, ok := conn.(*net.TCPConn)
if !ok {
return errors.New("Not a TCP connection")
}
if err := c.SetKeepAlive(true); err != nil {
return err
}
if err := c.SetKeepAlivePeriod(d); err != nil {
return err
}
return nil
}
func loadCertificate(certFile, keyFile string) (tls.Certificate, error) {
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err == nil {
return tlsCert, nil
}
glog.V(LWARNING).Infoln(err)
return tls.X509KeyPair([]byte(DefaultRawCert), []byte(DefaultRawKey))
}
This diff is collapsed.
package main
import (
"bufio"
"crypto/tls"
"encoding/base64"
"github.com/golang/glog"
"golang.org/x/net/http2"
"io"
"net"
"net/http"
"net/http/httputil"
"strings"
"time"
)
var (
http2Client *http.Client
)
func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) {
glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
var username, password string
if arg.User != nil {
username = arg.User.Username()
password, _ = arg.User.Password()
}
u, p, _ := basicAuth(req.Header.Get("Proxy-Authorization"))
req.Header.Del("Proxy-Authorization")
if (username != "" && u != username) || (password != "" && p != password) {
resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n"
if _, err := conn.Write([]byte(resp)); err != nil {
glog.V(LWARNING).Infof("[http] %s <- %s : %s", conn.RemoteAddr(), req.Host, err)
}
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, resp)
glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host)
return
}
if len(forwardArgs) > 0 {
last := forwardArgs[len(forwardArgs)-1]
if last.Protocol == "http" || last.Protocol == "" {
forwardHttpRequest(req, conn, arg)
return
}
}
c, err := connect(req.Host, "http", forwardArgs...)
if err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
conn.Write(b)
return
}
defer c.Close()
if req.Method == http.MethodConnect {
b := []byte("HTTP/1.1 200 Connection established\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
conn.Write(b)
} else {
req.Header.Del("Proxy-Connection")
req.Header.Set("Connection", "Keep-Alive")
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
return
}
}
glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
Transport(conn, c)
glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host)
}
func forwardHttpRequest(req *http.Request, conn net.Conn, arg Args) {
last := forwardArgs[len(forwardArgs)-1]
c, _, err := forwardChain(forwardArgs...)
if err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), last.Addr, err)
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), last.Addr, string(b))
conn.Write(b)
return
}
defer c.Close()
if last.User != nil {
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(last.User.String())))
}
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
return
}
glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
Transport(conn, c)
glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host)
return
}
type Http2ClientConn struct {
r io.Reader
w io.Writer
localAddr net.Addr
remoteAddr net.Addr
}
func (c *Http2ClientConn) Read(b []byte) (n int, err error) {
return c.r.Read(b)
}
func (c *Http2ClientConn) Write(b []byte) (n int, err error) {
return c.w.Write(b)
}
func (c *Http2ClientConn) Close() error {
if rc, ok := c.r.(io.ReadCloser); ok {
return rc.Close()
}
return nil
}
func (c *Http2ClientConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *Http2ClientConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *Http2ClientConn) SetDeadline(t time.Time) error {
return nil
}
func (c *Http2ClientConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *Http2ClientConn) SetWriteDeadline(t time.Time) error {
return nil
}
// init http2 client with target http2 proxy server addr, and forward chain chain
func initHttp2Client(host string, chain ...Args) {
tr := http2.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
// replace the default dialer with our forward chain.
conn, err := connect(host, "http2", chain...)
if err != nil {
return conn, err
}
return tls.Client(conn, cfg), nil
},
}
http2Client = &http.Client{Transport: &tr}
}
func handlerHttp2Request(w http.ResponseWriter, req *http.Request) {
target := req.Header.Get("gost-target")
if target == "" {
target = req.Host
}
glog.V(LINFO).Infof("[http2] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
c, err := connect(target, req.Header.Get("gost-protocol"), forwardArgs...)
if err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
w.Header().Set("Proxy-Agent", "gost/"+Version)
w.WriteHeader(http.StatusServiceUnavailable)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
return
}
defer c.Close()
glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target)
errc := make(chan error, 2)
if req.Method == http.MethodConnect {
w.Header().Set("Proxy-Agent", "gost/"+Version)
w.WriteHeader(http.StatusOK)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
// compatible with HTTP 1.x
if hj, ok := w.(http.Hijacker); ok && req.ProtoMajor == 1 {
// we take over the underly connection
conn, _, err := hj.Hijack()
if err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
return
}
defer conn.Close()
go Pipe(conn, c, errc)
go Pipe(c, conn, errc)
} else {
go Pipe(req.Body, c, errc)
go Pipe(c, flushWriter{w}, errc)
}
select {
case <-errc:
// glog.V(LWARNING).Infoln("exit", err)
}
} else {
req.Header.Set("Connection", "Keep-Alive")
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
return
}
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
glog.V(LWARNING).Infoln(err)
return
}
defer resp.Body.Close()
for k, v := range resp.Header {
for _, vv := range v {
w.Header().Add(k, vv)
}
}
w.WriteHeader(resp.StatusCode)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil {
glog.V(LWARNING).Infof("[http2] %s <- %s : %s", req.RemoteAddr, target, err)
}
}
glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target)
}
//func processSocks5OverHttp2()
func handleHttp2Transport(w http.ResponseWriter, req *http.Request) {
glog.V(LINFO).Infof("[http2] %s - %s", req.RemoteAddr, req.Host)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
}
func basicAuth(authInfo string) (username, password string, ok bool) {
if authInfo == "" {
return
}
if !strings.HasPrefix(authInfo, "Basic ") {
return
}
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authInfo, "Basic "))
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
type flushWriter struct {
w io.Writer
}
func (fw flushWriter) Write(p []byte) (n int, err error) {
n, err = fw.w.Write(p)
if err != nil {
glog.V(LWARNING).Infoln("flush writer:", err)
}
if f, ok := fw.w.(http.Flusher); ok {
f.Flush()
}
return
}
This diff is collapsed.
package main
import (
//"github.com/ginuerzh/gosocks5"
"crypto/tls"
"github.com/golang/glog"
"github.com/gorilla/websocket"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
)
type wsConn struct {
conn *websocket.Conn
rb []byte
}
func wsClient(scheme string, conn net.Conn, host string) (*wsConn, error) {
dialer := websocket.Dialer{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
HandshakeTimeout: time.Second * 90,
NetDial: func(net, addr string) (net.Conn, error) {
return conn, nil
},
}
u := url.URL{Scheme: scheme, Host: host, Path: "/ws"}
c, resp, err := dialer.Dial(u.String(), nil)
if err != nil {
return nil, err
}
resp.Body.Close()
return &wsConn{conn: c}, nil
}
func wsServer(conn *websocket.Conn) *wsConn {
return &wsConn{
conn: conn,
}
}
func (c *wsConn) Read(b []byte) (n int, err error) {
if len(c.rb) == 0 {
_, c.rb, err = c.conn.ReadMessage()
}
n = copy(b, c.rb)
c.rb = c.rb[n:]
//log.Println("ws r:", n)
return
}
func (c *wsConn) Write(b []byte) (n int, err error) {
err = c.conn.WriteMessage(websocket.BinaryMessage, b)
n = len(b)
//log.Println("ws w:", n)
return
}
func (c *wsConn) Close() error {
return c.conn.Close()
}
func (c *wsConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *wsConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (conn *wsConn) SetDeadline(t time.Time) error {
if err := conn.SetReadDeadline(t); err != nil {
return err
}
return conn.SetWriteDeadline(t)
}
func (c *wsConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *wsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
type ws struct {
upgrader websocket.Upgrader
arg Args
}
func NewWs(arg Args) *ws {
return &ws{
arg: arg,
upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
},
}
}
func (s *ws) handle(w http.ResponseWriter, r *http.Request) {
glog.V(LINFO).Infof("[ws] %s - %s", r.RemoteAddr, s.arg.Addr)
if glog.V(LDEBUG) {
dump, err := httputil.DumpRequest(r, false)
if err != nil {
glog.V(LWARNING).Infof("[ws] %s - %s : %s", r.RemoteAddr, s.arg.Addr, err)
} else {
glog.V(LDEBUG).Infof("[ws] %s - %s\n%s", r.RemoteAddr, s.arg.Addr, string(dump))
}
}
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
glog.V(LERROR).Infoln(err)
return
}
handleConn(wsServer(conn), s.arg)
}
func (s *ws) ListenAndServe() error {
sm := http.NewServeMux()
sm.HandleFunc("/ws", s.handle)
return http.ListenAndServe(s.arg.Addr, sm)
}
func (s *ws) listenAndServeTLS() error {
sm := http.NewServeMux()
sm.HandleFunc("/ws", s.handle)
server := &http.Server{
Addr: s.arg.Addr,
TLSConfig: &tls.Config{Certificates: []tls.Certificate{s.arg.Cert}},
Handler: sm,
}
return server.ListenAndServeTLS("", "")
}
package main
package gost
import (
"bufio"
"crypto/tls"
"encoding/base64"
"github.com/golang/glog"
"golang.org/x/net/http2"
//"bufio"
//"crypto/tls"
//"encoding/base64"
//"github.com/golang/glog"
//"golang.org/x/net/http2"
"io"
"net"
"net/http"
"net/http/httputil"
"strings"
//"net/http"
//"net/http/httputil"
//"strings"
"time"
)
var (
http2Client *http.Client
)
func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) {
glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
var username, password string
if arg.User != nil {
username = arg.User.Username()
password, _ = arg.User.Password()
}
u, p, _ := basicAuth(req.Header.Get("Proxy-Authorization"))
req.Header.Del("Proxy-Authorization")
if (username != "" && u != username) || (password != "" && p != password) {
resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n"
if _, err := conn.Write([]byte(resp)); err != nil {
glog.V(LWARNING).Infof("[http] %s <- %s : %s", conn.RemoteAddr(), req.Host, err)
}
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, resp)
glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host)
return
}
if len(forwardArgs) > 0 {
last := forwardArgs[len(forwardArgs)-1]
if last.Protocol == "http" || last.Protocol == "" {
forwardHttpRequest(req, conn, arg)
return
}
}
c, err := connect(req.Host, "http", forwardArgs...)
if err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
conn.Write(b)
return
}
defer c.Close()
if req.Method == http.MethodConnect {
b := []byte("HTTP/1.1 200 Connection established\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
conn.Write(b)
} else {
req.Header.Del("Proxy-Connection")
req.Header.Set("Connection", "Keep-Alive")
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
return
}
}
glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
Transport(conn, c)
glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host)
}
func forwardHttpRequest(req *http.Request, conn net.Conn, arg Args) {
last := forwardArgs[len(forwardArgs)-1]
c, _, err := forwardChain(forwardArgs...)
if err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), last.Addr, err)
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), last.Addr, string(b))
conn.Write(b)
return
}
defer c.Close()
if last.User != nil {
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(last.User.String())))
}
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
return
}
glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
Transport(conn, c)
glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host)
return
}
// http2 client connection, wrapped up just like a net.Conn
type Http2ClientConn struct {
r io.Reader
w io.Writer
......@@ -158,153 +56,3 @@ func (c *Http2ClientConn) SetReadDeadline(t time.Time) error {
func (c *Http2ClientConn) SetWriteDeadline(t time.Time) error {
return nil
}
// init http2 client with target http2 proxy server addr, and forward chain chain
func initHttp2Client(host string, chain ...Args) {
tr := http2.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
// replace the default dialer with our forward chain.
conn, err := connect(host, "http2", chain...)
if err != nil {
return conn, err
}
return tls.Client(conn, cfg), nil
},
}
http2Client = &http.Client{Transport: &tr}
}
func handlerHttp2Request(w http.ResponseWriter, req *http.Request) {
target := req.Header.Get("gost-target")
if target == "" {
target = req.Host
}
glog.V(LINFO).Infof("[http2] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
c, err := connect(target, req.Header.Get("gost-protocol"), forwardArgs...)
if err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
w.Header().Set("Proxy-Agent", "gost/"+Version)
w.WriteHeader(http.StatusServiceUnavailable)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
return
}
defer c.Close()
glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target)
errc := make(chan error, 2)
if req.Method == http.MethodConnect {
w.Header().Set("Proxy-Agent", "gost/"+Version)
w.WriteHeader(http.StatusOK)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
// compatible with HTTP 1.x
if hj, ok := w.(http.Hijacker); ok && req.ProtoMajor == 1 {
// we take over the underly connection
conn, _, err := hj.Hijack()
if err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
return
}
defer conn.Close()
go Pipe(conn, c, errc)
go Pipe(c, conn, errc)
} else {
go Pipe(req.Body, c, errc)
go Pipe(c, flushWriter{w}, errc)
}
select {
case <-errc:
// glog.V(LWARNING).Infoln("exit", err)
}
} else {
req.Header.Set("Connection", "Keep-Alive")
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
return
}
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
glog.V(LWARNING).Infoln(err)
return
}
defer resp.Body.Close()
for k, v := range resp.Header {
for _, vv := range v {
w.Header().Add(k, vv)
}
}
w.WriteHeader(resp.StatusCode)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil {
glog.V(LWARNING).Infof("[http2] %s <- %s : %s", req.RemoteAddr, target, err)
}
}
glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target)
}
//func processSocks5OverHttp2()
func handleHttp2Transport(w http.ResponseWriter, req *http.Request) {
glog.V(LINFO).Infof("[http2] %s - %s", req.RemoteAddr, req.Host)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
}
func basicAuth(authInfo string) (username, password string, ok bool) {
if authInfo == "" {
return
}
if !strings.HasPrefix(authInfo, "Basic ") {
return
}
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authInfo, "Basic "))
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
type flushWriter struct {
w io.Writer
}
func (fw flushWriter) Write(p []byte) (n int, err error) {
n, err = fw.w.Write(p)
if err != nil {
glog.V(LWARNING).Infoln("flush writer:", err)
}
if f, ok := fw.w.(http.Flusher); ok {
f.Flush()
}
return
}
package gost
import (
"net"
"net/url"
"strconv"
"strings"
)
// Proxy node represent a proxy
type ProxyNode struct {
Addr string // host:port
Protocol string // protocol: http/http2/socks5/ss
Transport string // transport: ws/wss/tls/tcp/udp/rtcp/rudp
Remote string // remote address, used by tcp/udp port forwarding
User *url.Userinfo // authentication for proxy
values url.Values
serverName string
conn net.Conn
}
// the format is [scheme://][user:pass@host]:port
func ParseProxyNode(s string) (node *ProxyNode, err error) {
if !strings.Contains(s, "://") {
s = "gost://" + s
}
u, err := url.Parse(s)
if err != nil {
return
}
node = &ProxyNode{
Addr: u.Host,
User: u.User,
values: u.Query(),
serverName: u.Host,
}
if strings.Contains(u.Host, ":") {
node.serverName, _, _ = net.SplitHostPort(u.Host)
}
schemes := strings.Split(u.Scheme, "+")
if len(schemes) == 1 {
node.Protocol = schemes[0]
node.Transport = schemes[0]
}
if len(schemes) == 2 {
node.Protocol = schemes[0]
node.Transport = schemes[1]
}
switch node.Transport {
case "ws", "wss", "tls":
case "https":
node.Protocol = "http"
node.Transport = "tls"
case "http2":
node.Protocol = "http2"
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding
node.Remote = strings.Trim(u.EscapedPath(), "/")
case "rtcp", "rudp": // started from v2.1, rtcp and rudp are for remote port forwarding
node.Remote = strings.Trim(u.EscapedPath(), "/")
default:
node.Transport = ""
}
switch node.Protocol {
case "http", "http2", "socks", "socks5", "ss":
default:
node.Protocol = ""
}
return
}
func (node *ProxyNode) insecureSkipVerify() bool {
s := node.values.Get("secure")
if secure, _ := strconv.ParseBool(s); secure {
return !secure
}
if n, _ := strconv.Atoi(s); n > 0 {
return false
}
return true
}
func (node *ProxyNode) certFile() string {
if cert := node.values.Get("cert"); cert != "" {
return cert
}
return DefaultCertFile
}
func (node *ProxyNode) keyFile() string {
if key := node.values.Get("key"); key != "" {
return key
}
return DefaultKeyFile
}
This diff is collapsed.
package main
import (
"bufio"
"flag"
"fmt"
"github.com/ginuerzh/gost"
"github.com/golang/glog"
"golang.org/x/net/http2"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
var (
proxyNodes stringlist
urls []string
)
func init() {
flag.Var(&proxyNodes, "F", "forward address, can make a forward chain")
flag.Parse()
if flag.NArg() == 0 {
log.Fatal("please specific at least one request URL")
}
urls = flag.Args()
if glog.V(gost.LVDEBUG) {
http2.VerboseLogs = true
}
}
type stringlist []string
func (list *stringlist) String() string {
return fmt.Sprintf("%s", *list)
}
func (list *stringlist) Set(value string) error {
*list = append(*list, value)
return nil
}
func main() {
chain := gost.NewProxyChain()
for _, s := range proxyNodes {
node, err := gost.ParseProxyNode(s)
if err != nil {
log.Fatal(err)
}
chain.AddProxyNode(*node)
}
chain.Init()
for _, u := range urls {
url, err := url.Parse(u)
if err != nil {
log.Println("Invalid url:", u)
continue
}
log.Println("GET", u)
conn, err := chain.Dial(url.Host)
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest("GET", u, nil)
if err != nil {
log.Fatal(err)
}
if err := req.Write(conn); err != nil {
log.Fatal(err)
}
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
header, _ := httputil.DumpResponse(resp, false)
log.Println(string(header))
}
}
// +build http2client
package main
import (
"crypto/tls"
"golang.org/x/net/http2"
"io"
"io/ioutil"
"log"
"net"
"net/http"
//"net/http/httputil"
"os"
"time"
)
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func main() {
tr := http2.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return tls.DialWithDialer(&net.Dialer{Timeout: 30 * time.Second}, "tcp", "localhost:8080", cfg)
},
}
client := http.Client{Transport: &tr}
pr, pw := io.Pipe()
req, err := http.NewRequest("CONNECT", "https://www.baidu.com", ioutil.NopCloser(pr))
req.ContentLength = -1
if err != nil {
log.Fatal(err)
}
/*
req := &http.Request{
Method: "CONNECT",
URL: &url.URL{Scheme: "https"},
Host: "www.baidu.com:443",
Header: make(http.Header),
}
*/
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
r, err := http.NewRequest("GET", "https://www.baidu.com", nil)
if err != nil {
log.Fatal(err)
}
r.Write(pw)
n, err := io.Copy(os.Stdout, resp.Body)
log.Fatalf("copied %d, %v", n, err)
}
package main
package gost
import (
//"github.com/ginuerzh/gosocks5"
"crypto/tls"
"github.com/golang/glog"
//"github.com/golang/glog"
"github.com/gorilla/websocket"
"net"
"net/http"
"net/http/httputil"
//"net/http"
//"net/http/httputil"
"net/url"
"time"
)
......@@ -19,8 +19,8 @@ type wsConn struct {
func wsClient(scheme string, conn net.Conn, host string) (*wsConn, error) {
dialer := websocket.Dialer{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
HandshakeTimeout: time.Second * 90,
NetDial: func(net, addr string) (net.Conn, error) {
......@@ -88,54 +88,3 @@ func (c *wsConn) SetReadDeadline(t time.Time) error {
func (c *wsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
type ws struct {
upgrader websocket.Upgrader
arg Args
}
func NewWs(arg Args) *ws {
return &ws{
arg: arg,
upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
},
}
}
func (s *ws) handle(w http.ResponseWriter, r *http.Request) {
glog.V(LINFO).Infof("[ws] %s - %s", r.RemoteAddr, s.arg.Addr)
if glog.V(LDEBUG) {
dump, err := httputil.DumpRequest(r, false)
if err != nil {
glog.V(LWARNING).Infof("[ws] %s - %s : %s", r.RemoteAddr, s.arg.Addr, err)
} else {
glog.V(LDEBUG).Infof("[ws] %s - %s\n%s", r.RemoteAddr, s.arg.Addr, string(dump))
}
}
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
glog.V(LERROR).Infoln(err)
return
}
handleConn(wsServer(conn), s.arg)
}
func (s *ws) ListenAndServe() error {
sm := http.NewServeMux()
sm.HandleFunc("/ws", s.handle)
return http.ListenAndServe(s.arg.Addr, sm)
}
func (s *ws) listenAndServeTLS() error {
sm := http.NewServeMux()
sm.HandleFunc("/ws", s.handle)
server := &http.Server{
Addr: s.arg.Addr,
TLSConfig: &tls.Config{Certificates: []tls.Certificate{s.arg.Cert}},
Handler: sm,
}
return server.ListenAndServeTLS("", "")
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment