diff --git a/README.md b/README.md index 496c124..fbb790a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ we can set up local listeners as proxy servers, and forward requests to internet |tls |√| |√| |transport client & server |kcp | |√|√| |transport client & server |unix |√| |√| |transport client & server -|websocket | | |√| |transport client only +|websocket |√| |√| |transport client only |simple-obfs | | |√| |transport client only |tcptun |√| | | |transport server only |udptun | |√| | |transport server only @@ -88,7 +88,7 @@ glider -h click to see details ```bash -glider 0.12.0 usage: +glider 0.12.1 usage: -checkdisabledonly check disabled fowarders only -checkinterval int @@ -143,7 +143,7 @@ glider 0.12.0 usage: verbose mode Available schemes: - listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcptun udptun tls unix kcp + listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcptun udptun tls ws unix kcp forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tls ws unix kcp simple-obfs Socks5 scheme: @@ -202,9 +202,12 @@ Proxy over tls server: tls://host:port?cert=PATH&key=PATH,socks5:// tls://host:port?cert=PATH&key=PATH,ss://method:pass@ -Websocket scheme: +Websocket client scheme: ws://host:port[/path][?host=HOST] +Websocket server scheme: + ws://:port[/path]?host=HOST + Websocket with a specified proxy protocol: ws://host:port[/path][?host=HOST],scheme:// ws://host:port[/path][?host=HOST],http://[user:pass@] diff --git a/config.go b/config.go index d713f3a..5ee3125 100644 --- a/config.go +++ b/config.go @@ -131,7 +131,7 @@ func usage() { fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Available schemes:\n") - fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcptun udptun tls unix kcp\n") + fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcptun udptun tls ws unix kcp\n") fmt.Fprintf(w, " forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tls ws unix kcp simple-obfs\n") fmt.Fprintf(w, "\n") @@ -205,10 +205,14 @@ func usage() { fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,ss://method:pass@\n") fmt.Fprintf(w, "\n") - fmt.Fprintf(w, "Websocket scheme:\n") + fmt.Fprintf(w, "Websocket client scheme:\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST]\n") fmt.Fprintf(w, "\n") + fmt.Fprintf(w, "Websocket server scheme:\n") + fmt.Fprintf(w, " ws://:port[/path]?host=HOST\n") + fmt.Fprintf(w, "\n") + fmt.Fprintf(w, "Websocket with a specified proxy protocol:\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],scheme://\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],http://[user:pass@]\n") diff --git a/config/glider.conf.example b/config/glider.conf.example index 71502d7..063b344 100644 --- a/config/glider.conf.example +++ b/config/glider.conf.example @@ -75,6 +75,9 @@ listen=socks5://:1080 # vless over tls server # listen=tls://:1234?cert=/path/to/cert&key=/path/to/key,vless://UUID@?fallback=127.0.0.1:80 +# vless over ws +# listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80 + # trojan server # listen=trojan://PASSWORD:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1 diff --git a/go.mod b/go.mod index b8593d8..34e2a61 100644 --- a/go.mod +++ b/go.mod @@ -10,10 +10,10 @@ require ( github.com/nadoo/ipset v0.3.0 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/xtaci/kcp-go/v5 v5.6.1 - golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee - golang.org/x/net v0.0.0-20201010224723-4f7140c49acb // indirect - golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 // indirect - golang.org/x/tools v0.0.0-20201014231627-1610a49f37af // indirect + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 // indirect + golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 // indirect + golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect ) diff --git a/go.sum b/go.sum index 28a8b68..6ba7537 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,8 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= -golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= @@ -141,8 +141,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -167,8 +167,8 @@ golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 h1:5jaG59Zhd+8ZXe8C+lgiAGqkOaZBruqrWclLkgAww34= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -177,8 +177,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200425043458-8463f397d07c h1:iHhCR0b26amDCiiO+kBguKZom9aMF+NrFxh9zeKR/XU= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201014231627-1610a49f37af h1:VIUWFyOgzG3c0t9KYop5Ybp4m56LupfOnFYX7Ipnz+I= -golang.org/x/tools v0.0.0-20201014231627-1610a49f37af/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ= +golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/main.go b/main.go index 9619197..9456958 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,7 @@ import ( ) var ( - version = "0.12.0" + version = "0.12.1" config = parseConfig() ) diff --git a/proxy/ws/client.go b/proxy/ws/client.go index aa88b46..9b4d1ee 100644 --- a/proxy/ws/client.go +++ b/proxy/ws/client.go @@ -2,50 +2,62 @@ package ws import ( "bufio" - "crypto/rand" - "crypto/sha1" - "encoding/base64" "errors" "io" "net" "net/textproto" - "strings" "github.com/nadoo/glider/pool" + "github.com/nadoo/glider/proxy" ) -var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") - -// Client is ws client struct. -type Client struct { - host string - path string +func init() { + proxy.RegisterDialer("ws", NewWSDialer) } -// Conn is a connection to ws server. -type Conn struct { +// NewWSDialer returns a ws proxy dialer. +func NewWSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { + return NewWS(s, d, nil) +} + +// Addr returns forwarder's address. +func (s *WS) Addr() string { + if s.addr == "" { + return s.dialer.Addr() + } + return s.addr +} + +// Dial connects to the address addr on the network net via the proxy. +func (s *WS) Dial(network, addr string) (net.Conn, error) { + rc, err := s.dialer.Dial("tcp", s.addr) + if err != nil { + return nil, err + } + + return s.NewClientConn(rc) +} + +// DialUDP connects to the given address via the proxy. +func (s *WS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { + return nil, nil, errors.New("[ws] ws client does not support udp now") +} + +// ClientConn is a connection to ws server. +type ClientConn struct { net.Conn reader io.Reader writer io.Writer } -// NewClient creates a new ws client. -func NewClient(host, path string) (*Client, error) { - if path == "" { - path = "/" - } - c := &Client{host: host, path: path} - return c, nil -} - -// NewConn creates a new ws client connection. -func (c *Client) NewConn(rc net.Conn, target string) (*Conn, error) { - conn := &Conn{Conn: rc} - return conn, conn.Handshake(c.host, c.path) +// NewClientConn creates a new ws client connection. +func (s *WS) NewClientConn(rc net.Conn) (*ClientConn, error) { + conn := &ClientConn{Conn: rc} + return conn, conn.Handshake(s.host, s.path) } // Handshake handshakes with the server using HTTP to request a protocol upgrade. -func (c *Conn) Handshake(host, path string) error { +func (c *ClientConn) Handshake(host, path string) error { clientKey := generateClientKey() buf := pool.GetWriteBuffer() @@ -89,42 +101,16 @@ func (c *Conn) Handshake(host, path string) error { return nil } -func (c *Conn) Write(b []byte) (n int, err error) { +func (c *ClientConn) Write(b []byte) (n int, err error) { if c.writer == nil { - c.writer = FrameWriter(c.Conn) + c.writer = FrameWriter(c.Conn, true) } return c.writer.Write(b) } -func (c *Conn) Read(b []byte) (n int, err error) { +func (c *ClientConn) Read(b []byte) (n int, err error) { if c.reader == nil { - c.reader = FrameReader(c.Conn) + c.reader = FrameReader(c.Conn, true) } return c.reader.Read(b) } - -// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. -// TODO: move to separate http lib package for reuse(also for http proxy module) -func parseFirstLine(line string) (r1, r2, r3 string, ok bool) { - s1 := strings.Index(line, " ") - s2 := strings.Index(line[s1+1:], " ") - if s1 < 0 || s2 < 0 { - return - } - s2 += s1 + 1 - return line[:s1], line[s1+1 : s2], line[s2+1:], true -} - -func generateClientKey() string { - p := pool.GetBuffer(16) - defer pool.PutBuffer(p) - rand.Read(p) - return base64.StdEncoding.EncodeToString(p) -} - -func computeServerKey(clientKey string) string { - h := sha1.New() - h.Write([]byte(clientKey)) - h.Write(keyGUID) - return base64.StdEncoding.EncodeToString(h.Sum(nil)) -} diff --git a/proxy/ws/frame.go b/proxy/ws/frame.go index df1777c..d63f2da 100644 --- a/proxy/ws/frame.go +++ b/proxy/ws/frame.go @@ -30,28 +30,32 @@ import ( ) const ( - defaultFrameSize = 4096 - maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask - maskKeyLen = 4 + defaultFrameSize = 4096 + maxHeaderSize = 2 + 8 + 4 // Fixed header + length + mask + // byte 0 finalBit byte = 1 << 7 - maskBit byte = 1 << 7 opCodeBinary byte = 2 + + // byte 1 + maskBit byte = 1 << 7 ) type frameWriter struct { io.Writer buf []byte - maskKey [maskKeyLen]byte + client bool + maskKey [4]byte } // FrameWriter returns a frame writer. -func FrameWriter(w io.Writer) io.Writer { +func FrameWriter(w io.Writer, client bool) io.Writer { n := rand.Uint32() return &frameWriter{ Writer: w, - buf: make([]byte, maxFrameHeaderSize+defaultFrameSize), - maskKey: [...]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}, + buf: make([]byte, maxHeaderSize+defaultFrameSize), + client: client, + maskKey: [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}, } } @@ -63,13 +67,18 @@ func (w *frameWriter) Write(b []byte) (int, error) { func (w *frameWriter) ReadFrom(r io.Reader) (n int64, err error) { for { buf := w.buf - payloadBuf := buf[maxFrameHeaderSize:] + payloadBuf := buf[maxHeaderSize:] nr, er := r.Read(payloadBuf) + if nr > 0 { n += int64(nr) - buf[0] = finalBit | opCodeBinary - buf[1] = maskBit + buf[0] = opCodeBinary + buf[1] = 0 + if w.client { + buf[0] |= finalBit + buf[1] = maskBit + } lengthFieldLen := 0 switch { @@ -92,17 +101,20 @@ func (w *frameWriter) ReadFrom(r io.Reader) (n int64, err error) { break } - // maskkey - _, ew = w.Writer.Write(w.maskKey[:]) - if ew != nil { - err = ew - break - } - - // payload payloadBuf = payloadBuf[:nr] - for i := range payloadBuf { - payloadBuf[i] = payloadBuf[i] ^ w.maskKey[i%4] + + if w.client { + // maskkey + _, ew = w.Writer.Write(w.maskKey[:]) + if ew != nil { + err = ew + break + } + + // payload mask + for i := range payloadBuf { + payloadBuf[i] = payloadBuf[i] ^ w.maskKey[i%4] + } } _, ew = w.Writer.Write(payloadBuf) @@ -126,17 +138,21 @@ func (w *frameWriter) ReadFrom(r io.Reader) (n int64, err error) { type frameReader struct { io.Reader - buf [8]byte - left int64 + buf [8]byte + left int64 + server bool + maskKey [4]byte + maskOffset int } // FrameReader returns a chunked reader. -func FrameReader(r io.Reader) io.Reader { - return &frameReader{Reader: r} +func FrameReader(r io.Reader, client bool) io.Reader { + return &frameReader{Reader: r, server: !client} } func (r *frameReader) Read(b []byte) (int, error) { if r.left == 0 { + // get msg header _, err := io.ReadFull(r.Reader, r.buf[:2]) if err != nil { @@ -145,7 +161,8 @@ func (r *frameReader) Read(b []byte) (int, error) { // final := r.buf[0]&finalBit != 0 // frameType := int(r.buf[0] & 0xf) - // mask := r.buf[1]&maskBit != 0 + // r.mask = r.buf[1]&maskBit != 0 + r.left = int64(r.buf[1] & 0x7f) switch r.left { case 126: @@ -161,6 +178,14 @@ func (r *frameReader) Read(b []byte) (int, error) { } r.left = int64(binary.BigEndian.Uint64(r.buf[:8])) } + + if r.server { + _, err := io.ReadFull(r.Reader, r.maskKey[:]) + if err != nil { + return 0, err + } + r.maskOffset = 0 + } } readLen := int64(len(b)) @@ -173,6 +198,13 @@ func (r *frameReader) Read(b []byte) (int, error) { return 0, err } + if r.server { + for i := range b[:m] { + b[i] = b[i] ^ r.maskKey[(i+r.maskOffset)%4] + } + r.maskOffset = (m + r.maskOffset) % 4 + } + r.left -= int64(m) return m, err } diff --git a/proxy/ws/server.go b/proxy/ws/server.go new file mode 100644 index 0000000..53ff9cc --- /dev/null +++ b/proxy/ws/server.go @@ -0,0 +1,144 @@ +package ws + +import ( + "bufio" + "errors" + "io" + "net" + "net/textproto" + "strings" + + "github.com/nadoo/glider/log" + "github.com/nadoo/glider/pool" + "github.com/nadoo/glider/proxy" +) + +func init() { + proxy.RegisterServer("ws", NewWSServer) +} + +// NewWSServer returns a ws transport server. +func NewWSServer(s string, p proxy.Proxy) (proxy.Server, error) { + transport := strings.Split(s, ",") + + // prepare transport listener + // TODO: check here + if len(transport) < 2 { + return nil, errors.New("[tls] malformd listener:" + s) + } + + w, err := NewWS(transport[0], nil, p) + if err != nil { + return nil, err + } + + w.server, err = proxy.ServerFromURL(transport[1], p) + if err != nil { + return nil, err + } + + return w, nil +} + +// ListenAndServe listens on server's addr and serves connections. +func (s *WS) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + log.F("[ws] failed to listen on %s: %v", s.addr, err) + return + } + defer l.Close() + + log.F("[ws] listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + log.F("[ws] failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +// Serve serves a connection. +func (s *WS) Serve(c net.Conn) { + // we know the internal server will close the connection after serve + // defer c.Close() + + if s.server != nil { + sc, err := s.NewServerConn(c) + if err != nil { + return + } + s.server.Serve(sc) + } +} + +// ServerConn is a connection to ws client. +type ServerConn struct { + net.Conn + reader io.Reader + writer io.Writer +} + +// NewServerConn creates a new ws server connection. +func (s *WS) NewServerConn(rc net.Conn) (*ServerConn, error) { + sc := &ServerConn{Conn: rc} + err := sc.Handshake(s.host, s.path) + return sc, err +} + +// Handshake handshakes with the client. +func (c *ServerConn) Handshake(host, path string) error { + tpr := textproto.NewReader(bufio.NewReader(c.Conn)) + line, err := tpr.ReadLine() + if err != nil { + return err + } + + _, path, _, ok := parseFirstLine(line) + if !ok || path != path { + return errors.New("[ws] error in ws handshake parseFirstLine") + } + + reqHeader, err := tpr.ReadMIMEHeader() + if err != nil { + return err + } + + if reqHeader.Get("Host") != host { + return errors.New("[ws] got wrong host") + } + + clientKey := reqHeader.Get("Sec-WebSocket-Key") + serverKey := computeServerKey(clientKey) + + buf := pool.GetWriteBuffer() + defer pool.PutWriteBuffer(buf) + + buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n") + buf.WriteString("Upgrade: websocket\r\n") + buf.WriteString("Connection: Upgrade\r\n") + buf.WriteString("Sec-WebSocket-Accept: " + serverKey + "\r\n") + buf.WriteString("Sec-WebSocket-Protocol: binary\r\n") + buf.WriteString(("\r\n")) + + _, err = c.Conn.Write(buf.Bytes()) + return err +} + +func (c *ServerConn) Write(b []byte) (n int, err error) { + if c.writer == nil { + c.writer = FrameWriter(c.Conn, false) + } + return c.writer.Write(b) +} + +func (c *ServerConn) Read(b []byte) (n int, err error) { + if c.reader == nil { + c.reader = FrameReader(c.Conn, false) + } + return c.reader.Read(b) +} diff --git a/proxy/ws/ws.go b/proxy/ws/ws.go index 39ecba2..34b00f8 100644 --- a/proxy/ws/ws.go +++ b/proxy/ws/ws.go @@ -2,29 +2,33 @@ package ws import ( - "errors" + "crypto/rand" + "crypto/sha1" + "encoding/base64" "net" "net/url" + "strings" "github.com/nadoo/glider/log" + "github.com/nadoo/glider/pool" "github.com/nadoo/glider/proxy" ) +var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + // WS is the base ws proxy struct. type WS struct { dialer proxy.Dialer + proxy proxy.Proxy addr string host string + path string - client *Client -} - -func init() { - proxy.RegisterDialer("ws", NewWSDialer) + server proxy.Server } // NewWS returns a websocket proxy. -func NewWS(s string, d proxy.Dialer) (*WS, error) { +func NewWS(s string, d proxy.Dialer, p proxy.Proxy) (*WS, error) { u, err := url.Parse(s) if err != nil { log.F("[ws] parse url err: %s", err) @@ -38,49 +42,47 @@ func NewWS(s string, d proxy.Dialer) (*WS, error) { addr = d.Addr() } - p := &WS{ + w := &WS{ dialer: d, + proxy: p, addr: addr, host: u.Query().Get("host"), + path: u.Path, } - if p.host == "" { - p.host, _, _ = net.SplitHostPort(addr) + if w.host == "" { + w.host, _, _ = net.SplitHostPort(addr) } - p.client, err = NewClient(p.host, u.Path) - if err != nil { - log.F("[ws] create ws client error: %s", err) - return nil, err + if w.path == "" { + w.path = "/" } - return p, nil + return w, nil } -// NewWSDialer returns a ws proxy dialer. -func NewWSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { - return NewWS(s, d) -} - -// Addr returns forwarder's address. -func (s *WS) Addr() string { - if s.addr == "" { - return s.dialer.Addr() +// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. +// TODO: move to separate http lib package for reuse(also for http proxy module) +func parseFirstLine(line string) (r1, r2, r3 string, ok bool) { + s1 := strings.Index(line, " ") + s2 := strings.Index(line[s1+1:], " ") + if s1 < 0 || s2 < 0 { + return } - return s.addr + s2 += s1 + 1 + return line[:s1], line[s1+1 : s2], line[s2+1:], true } -// Dial connects to the address addr on the network net via the proxy. -func (s *WS) Dial(network, addr string) (net.Conn, error) { - rc, err := s.dialer.Dial("tcp", s.addr) - if err != nil { - return nil, err - } - - return s.client.NewConn(rc, addr) +func generateClientKey() string { + p := pool.GetBuffer(16) + defer pool.PutBuffer(p) + rand.Read(p) + return base64.StdEncoding.EncodeToString(p) } -// DialUDP connects to the given address via the proxy. -func (s *WS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { - return nil, nil, errors.New("[ws] ws client does not support udp now") +func computeServerKey(clientKey string) string { + h := sha1.New() + h.Write([]byte(clientKey)) + h.Write(keyGUID) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) } diff --git a/rule/group.go b/rule/group.go index 52808e7..9a912cb 100644 --- a/rule/group.go +++ b/rule/group.go @@ -236,7 +236,7 @@ func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf [] rc.SetDeadline(time.Now().Add(timeout)) } - _, err = io.WriteString(rc, "GET / HTTP/1.1\r\n\r\n") + _, err = io.WriteString(rc, "GET / HTTP/1.1\r\nHost:"+website+"\r\nConnection: close"+"\r\n\r\n") if err != nil { log.F("[check] %s(%d) -> %s, FAILED. error in write: %s", fwdr.Addr(), fwdr.Priority(), website, err) fwdr.Disable()