mirror of
				https://github.com/nadoo/glider.git
				synced 2025-10-30 21:35:51 +08:00 
			
		
		
		
	http: support auth in server mode. #84
This commit is contained in:
		
							parent
							
								
									2560f6727f
								
							
						
					
					
						commit
						ef8cdfa703
					
				
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							| @ -116,7 +116,7 @@ glider -config CONFIGPATH -listen :8080 -verbose | ||||
| ## Usage | ||||
| 
 | ||||
| ```bash | ||||
| glider 0.8.0 usage: | ||||
| glider 0.9.0 usage: | ||||
|   -checkinterval int | ||||
|         proxy check interval(seconds) (default 30) | ||||
|   -checktimeout int | ||||
| @ -191,6 +191,7 @@ Available methods for ss: | ||||
|     AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5 | ||||
|   Alias: | ||||
|     chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305 | ||||
|   Plain: DUMMY | ||||
| 
 | ||||
| SSR scheme: | ||||
|   ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz | ||||
| @ -282,17 +283,14 @@ Examples: | ||||
|   glider -config glider.conf | ||||
|     -run glider with specified config file. | ||||
| 
 | ||||
|   glider -config glider.conf -rulefile office.rule -rulefile home.rule | ||||
|     -run glider with specified global config file and rule config files. | ||||
|   glider -listen :8443 -verbose | ||||
|     -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode. | ||||
| 
 | ||||
|   glider -listen :8443 | ||||
|     -listen on :8443, serve as http/socks5 proxy on the same port. | ||||
| 
 | ||||
|   glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 | ||||
|   glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose | ||||
|     -listen on 0.0.0.0:8443 as a ss server. | ||||
| 
 | ||||
|   glider -listen socks5://:1080 -verbose | ||||
|     -listen on :1080 as a socks5 proxy server, in verbose mode. | ||||
|   glider -listen socks5://user1:pass1@:1080 -verbose | ||||
|     -listen on :1080 as a socks5 proxy server, enable authentication. | ||||
| 
 | ||||
|   glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose | ||||
|     -listen on :443 as a https(http over tls) proxy server. | ||||
|  | ||||
							
								
								
									
										13
									
								
								conf.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								conf.go
									
									
									
									
									
								
							| @ -255,17 +255,14 @@ func usage() { | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -config glider.conf\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -run glider with specified config file.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -run glider with specified global config file and rule config files.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen :8443 -verbose\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen :8443\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -listen on :8443, serve as http/socks5 proxy on the same port.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -listen on 0.0.0.0:8443 as a ss server.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen socks5://:1080 -verbose\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -listen on :1080 as a socks5 proxy server, in verbose mode.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen socks5://user1:pass1@:1080 -verbose\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -listen on :1080 as a socks5 proxy server, enable authentication.\n") | ||||
| 	fmt.Fprintf(os.Stderr, "\n") | ||||
| 	fmt.Fprintf(os.Stderr, "  "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n") | ||||
| 	fmt.Fprintf(os.Stderr, "    -listen on :443 as a https(http over tls) proxy server.\n") | ||||
|  | ||||
							
								
								
									
										81
									
								
								proxy/http/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								proxy/http/client.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| package http | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"net" | ||||
| 	"net/textproto" | ||||
| 
 | ||||
| 	"github.com/nadoo/glider/common/conn" | ||||
| 	"github.com/nadoo/glider/common/log" | ||||
| 	"github.com/nadoo/glider/proxy" | ||||
| ) | ||||
| 
 | ||||
| // NewHTTPDialer returns a http proxy dialer.
 | ||||
| func NewHTTPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { | ||||
| 	return NewHTTP(s, d, nil) | ||||
| } | ||||
| 
 | ||||
| // Addr returns forwarder's address.
 | ||||
| func (s *HTTP) 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 *HTTP) Dial(network, addr string) (net.Conn, error) { | ||||
| 	rc, err := s.dialer.Dial(network, s.addr) | ||||
| 	if err != nil { | ||||
| 		log.F("[http] dial to %s error: %s", s.addr, err) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var buf bytes.Buffer | ||||
| 	buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") | ||||
| 	buf.WriteString("Host: " + addr + "\r\n") | ||||
| 	buf.WriteString("Proxy-Connection: Keep-Alive\r\n") | ||||
| 
 | ||||
| 	if s.user != "" && s.password != "" { | ||||
| 		auth := s.user + ":" + s.password | ||||
| 		buf.Write([]byte("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n")) | ||||
| 	} | ||||
| 
 | ||||
| 	// header ended
 | ||||
| 	buf.WriteString("\r\n") | ||||
| 	_, err = rc.Write(buf.Bytes()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	c := conn.NewConn(rc) | ||||
| 	tpr := textproto.NewReader(c.Reader()) | ||||
| 	line, err := tpr.ReadLine() | ||||
| 	if err != nil { | ||||
| 		return c, err | ||||
| 	} | ||||
| 
 | ||||
| 	_, code, _, ok := parseStartLine(line) | ||||
| 	if ok && code == "200" { | ||||
| 		tpr.ReadMIMEHeader() | ||||
| 		return c, err | ||||
| 	} | ||||
| 
 | ||||
| 	switch code { | ||||
| 	case "403": | ||||
| 		log.F("[http] 'CONNECT' to ports other than 443 are not allowed by proxy %s", s.addr) | ||||
| 	case "405": | ||||
| 		log.F("[http] 'CONNECT' method not allowed by proxy %s", s.addr) | ||||
| 	case "407": | ||||
| 		log.F("[http] authencation needed by proxy %s", s.addr) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, errors.New("[http] can not connect remote address: " + addr + ". error code: " + code) | ||||
| } | ||||
| 
 | ||||
| // DialUDP connects to the given address via the proxy.
 | ||||
| func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { | ||||
| 	return nil, nil, errors.New("http client does not support udp") | ||||
| } | ||||
| @ -5,31 +5,24 @@ | ||||
| package http | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/textproto" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/nadoo/glider/common/conn" | ||||
| 	"github.com/nadoo/glider/common/log" | ||||
| 	"github.com/nadoo/glider/proxy" | ||||
| ) | ||||
| 
 | ||||
| // HTTP struct.
 | ||||
| type HTTP struct { | ||||
| 	dialer             proxy.Dialer | ||||
| 	proxy              proxy.Proxy | ||||
| 	addr               string | ||||
| 	user               string | ||||
| 	password           string | ||||
| 	pretendAsWebServer bool | ||||
| 	dialer   proxy.Dialer | ||||
| 	proxy    proxy.Proxy | ||||
| 	addr     string | ||||
| 	user     string | ||||
| 	password string | ||||
| 	pretend  bool | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| @ -50,240 +43,23 @@ func NewHTTP(s string, d proxy.Dialer, p proxy.Proxy) (*HTTP, error) { | ||||
| 	pass, _ := u.User.Password() | ||||
| 
 | ||||
| 	h := &HTTP{ | ||||
| 		dialer:             d, | ||||
| 		proxy:              p, | ||||
| 		addr:               addr, | ||||
| 		user:               user, | ||||
| 		password:           pass, | ||||
| 		pretendAsWebServer: false, | ||||
| 		dialer:   d, | ||||
| 		proxy:    p, | ||||
| 		addr:     addr, | ||||
| 		user:     user, | ||||
| 		password: pass, | ||||
| 		pretend:  false, | ||||
| 	} | ||||
| 
 | ||||
| 	pretend := u.Query().Get("pretend") | ||||
| 	if pretend == "true" { | ||||
| 		h.pretendAsWebServer = true | ||||
| 	if u.Query().Get("pretend") == "true" { | ||||
| 		h.pretend = true | ||||
| 	} | ||||
| 
 | ||||
| 	return h, nil | ||||
| } | ||||
| 
 | ||||
| // NewHTTPDialer returns a http proxy dialer.
 | ||||
| func NewHTTPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { | ||||
| 	return NewHTTP(s, d, nil) | ||||
| } | ||||
| 
 | ||||
| // NewHTTPServer returns a http proxy server.
 | ||||
| func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) { | ||||
| 	return NewHTTP(s, nil, p) | ||||
| } | ||||
| 
 | ||||
| // ListenAndServe listens on server's addr and serves connections.
 | ||||
| func (s *HTTP) ListenAndServe() { | ||||
| 	l, err := net.Listen("tcp", s.addr) | ||||
| 	if err != nil { | ||||
| 		log.F("[http] failed to listen on %s: %v", s.addr, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer l.Close() | ||||
| 
 | ||||
| 	log.F("[http] listening TCP on %s", s.addr) | ||||
| 
 | ||||
| 	for { | ||||
| 		c, err := l.Accept() | ||||
| 		if err != nil { | ||||
| 			log.F("[http] failed to accept: %v", err) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		go s.Serve(c) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Serve serves a connection.
 | ||||
| func (s *HTTP) Serve(c net.Conn) { | ||||
| 	defer c.Close() | ||||
| 
 | ||||
| 	if c, ok := c.(*net.TCPConn); ok { | ||||
| 		c.SetKeepAlive(true) | ||||
| 	} | ||||
| 
 | ||||
| 	reqR := bufio.NewReader(c) | ||||
| 	reqTP := textproto.NewReader(reqR) | ||||
| 	method, requestURI, proto, ok := parseStartLine(reqTP) | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if s.pretendAsWebServer { | ||||
| 		fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", proto) | ||||
| 		log.F("[http pretender] being accessed as web server from %s", c.RemoteAddr().String()) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if method == "CONNECT" { | ||||
| 		s.servHTTPS(method, requestURI, proto, c) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	reqHeader, err := reqTP.ReadMIMEHeader() | ||||
| 	if err != nil { | ||||
| 		log.F("[http] read header error:%s", err) | ||||
| 		return | ||||
| 	} | ||||
| 	cleanHeaders(reqHeader) | ||||
| 
 | ||||
| 	// tell the remote server not to keep alive
 | ||||
| 	reqHeader.Set("Connection", "close") | ||||
| 
 | ||||
| 	u, err := url.ParseRequestURI(requestURI) | ||||
| 	if err != nil { | ||||
| 		log.F("[http] parse request url error: %s", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	tgt := u.Host | ||||
| 	if !strings.Contains(u.Host, ":") { | ||||
| 		tgt += ":80" | ||||
| 	} | ||||
| 
 | ||||
| 	rc, p, err := s.proxy.Dial("tcp", tgt) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto) | ||||
| 		log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer rc.Close() | ||||
| 
 | ||||
| 	// GET http://example.com/a/index.htm HTTP/1.1 -->
 | ||||
| 	// GET /a/index.htm HTTP/1.1
 | ||||
| 	u.Scheme, u.Host = "", "" | ||||
| 	uri := u.String() | ||||
| 
 | ||||
| 	var buf bytes.Buffer | ||||
| 	writeStartLine(&buf, method, uri, proto) | ||||
| 	writeHeaders(&buf, reqHeader) | ||||
| 
 | ||||
| 	// send request to remote server
 | ||||
| 	rc.Write(buf.Bytes()) | ||||
| 
 | ||||
| 	// copy the left request bytes to remote server. eg. length specificed or chunked body
 | ||||
| 	go func() { | ||||
| 		if _, err := reqR.Peek(1); err == nil { | ||||
| 			io.Copy(rc, reqR) | ||||
| 			rc.SetDeadline(time.Now()) | ||||
| 			c.SetDeadline(time.Now()) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	respR := bufio.NewReader(rc) | ||||
| 	respTP := textproto.NewReader(respR) | ||||
| 	proto, code, status, ok := parseStartLine(respTP) | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	respHeader, err := respTP.ReadMIMEHeader() | ||||
| 	if err != nil { | ||||
| 		log.F("[http] %s <-> %s via %s, read header error: %v", c.RemoteAddr(), tgt, p, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	respHeader.Set("Proxy-Connection", "close") | ||||
| 	respHeader.Set("Connection", "close") | ||||
| 
 | ||||
| 	buf.Reset() | ||||
| 	writeStartLine(&buf, proto, code, status) | ||||
| 	writeHeaders(&buf, respHeader) | ||||
| 
 | ||||
| 	log.F("[http] %s <-> %s via %s", c.RemoteAddr(), tgt, p) | ||||
| 	c.Write(buf.Bytes()) | ||||
| 
 | ||||
| 	io.Copy(c, respR) | ||||
| } | ||||
| 
 | ||||
| func (s *HTTP) servHTTPS(method, requestURI, proto string, c net.Conn) { | ||||
| 	rc, p, err := s.proxy.Dial("tcp", requestURI) | ||||
| 	if err != nil { | ||||
| 		c.Write([]byte(proto)) | ||||
| 		c.Write([]byte(" 502 ERROR\r\n\r\n")) | ||||
| 		log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), requestURI, p, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) | ||||
| 
 | ||||
| 	log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), requestURI, p) | ||||
| 
 | ||||
| 	_, _, err = conn.Relay(c, rc) | ||||
| 	if err != nil { | ||||
| 		if err, ok := err.(net.Error); ok && err.Timeout() { | ||||
| 			return // ignore i/o timeout
 | ||||
| 		} | ||||
| 		log.F("[http] relay error: %v", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Addr returns forwarder's address.
 | ||||
| func (s *HTTP) 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 *HTTP) Dial(network, addr string) (net.Conn, error) { | ||||
| 	rc, err := s.dialer.Dial(network, s.addr) | ||||
| 	if err != nil { | ||||
| 		log.F("[http] dial to %s error: %s", s.addr, err) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var buf bytes.Buffer | ||||
| 	buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") | ||||
| 	buf.WriteString("Host: " + addr + "\r\n") | ||||
| 	buf.WriteString("Proxy-Connection: Keep-Alive\r\n") | ||||
| 
 | ||||
| 	if s.user != "" && s.password != "" { | ||||
| 		auth := s.user + ":" + s.password | ||||
| 		buf.Write([]byte("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n")) | ||||
| 	} | ||||
| 
 | ||||
| 	// header ended
 | ||||
| 	buf.WriteString("\r\n") | ||||
| 	_, err = rc.Write(buf.Bytes()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	c := conn.NewConn(rc) | ||||
| 	tpr := textproto.NewReader(c.Reader()) | ||||
| 	_, code, _, ok := parseStartLine(tpr) | ||||
| 	if ok && code == "200" { | ||||
| 		tpr.ReadMIMEHeader() | ||||
| 		return c, err | ||||
| 	} | ||||
| 
 | ||||
| 	if code == "407" { | ||||
| 		log.F("[http] authencation needed by proxy %s", s.addr) | ||||
| 	} else if code == "405" { | ||||
| 		log.F("[http] 'CONNECT' method not allowed by proxy %s", s.addr) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, errors.New("[http] can not connect remote address: " + addr + ". error code: " + code) | ||||
| } | ||||
| 
 | ||||
| // DialUDP connects to the given address via the proxy.
 | ||||
| func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { | ||||
| 	return nil, nil, errors.New("http client does not support udp") | ||||
| } | ||||
| 
 | ||||
| // parseStartLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts.
 | ||||
| func parseStartLine(tp *textproto.Reader) (r1, r2, r3 string, ok bool) { | ||||
| 	line, err := tp.ReadLine() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| func parseStartLine(line string) (r1, r2, r3 string, ok bool) { | ||||
| 	s1 := strings.Index(line, " ") | ||||
| 	s2 := strings.Index(line[s1+1:], " ") | ||||
| 	if s1 < 0 || s2 < 0 { | ||||
| @ -317,3 +93,22 @@ func writeHeaders(buf *bytes.Buffer, header textproto.MIMEHeader) { | ||||
| 	} | ||||
| 	buf.WriteString("\r\n") | ||||
| } | ||||
| 
 | ||||
| func extractUserPass(auth string) (username, password string, ok bool) { | ||||
| 	if !strings.HasPrefix(auth, "Basic ") { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	b, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	s := string(b) | ||||
| 	idx := strings.IndexByte(s, ':') | ||||
| 	if idx < 0 { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	return s[:idx], s[idx+1:], true | ||||
| } | ||||
|  | ||||
							
								
								
									
										113
									
								
								proxy/http/request.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								proxy/http/request.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | ||||
| package http | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"net/textproto" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/nadoo/glider/common/log" | ||||
| ) | ||||
| 
 | ||||
| // Methods are http methods from rfc.
 | ||||
| // https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase
 | ||||
| var Methods = [...][]byte{ | ||||
| 	[]byte("GET"), | ||||
| 	[]byte("POST"), | ||||
| 	[]byte("PUT"), | ||||
| 	[]byte("DELETE"), | ||||
| 	[]byte("CONNECT"), | ||||
| 	[]byte("HEAD"), | ||||
| 	[]byte("OPTIONS"), | ||||
| 	[]byte("TRACE"), | ||||
| 	[]byte("PATCH"), | ||||
| } | ||||
| 
 | ||||
| type request struct { | ||||
| 	method string | ||||
| 	uri    string | ||||
| 	proto  string | ||||
| 	auth   string | ||||
| 	header textproto.MIMEHeader | ||||
| 
 | ||||
| 	target string // target host with port
 | ||||
| 	ruri   string // relative uri
 | ||||
| 	absuri string // absolute uri
 | ||||
| } | ||||
| 
 | ||||
| func parseRequest(r *bufio.Reader) (*request, error) { | ||||
| 	tpr := textproto.NewReader(r) | ||||
| 	line, err := tpr.ReadLine() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	method, uri, proto, ok := parseStartLine(line) | ||||
| 	if !ok { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	header, err := tpr.ReadMIMEHeader() | ||||
| 	if err != nil { | ||||
| 		log.F("[http] read header error:%s", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	auth := header.Get("Proxy-Authorization") | ||||
| 
 | ||||
| 	cleanHeaders(header) | ||||
| 	header.Set("Connection", "close") | ||||
| 
 | ||||
| 	u, err := url.ParseRequestURI(uri) | ||||
| 	if err != nil { | ||||
| 		log.F("[http] parse request url error: %s, uri: %s", err, uri) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var tgt = u.Host | ||||
| 	if !strings.Contains(u.Host, ":") { | ||||
| 		tgt += ":80" | ||||
| 	} | ||||
| 
 | ||||
| 	req := &request{ | ||||
| 		method: method, | ||||
| 		uri:    uri, | ||||
| 		proto:  proto, | ||||
| 		auth:   auth, | ||||
| 		header: header, | ||||
| 		target: tgt, | ||||
| 	} | ||||
| 
 | ||||
| 	if u.IsAbs() { | ||||
| 		req.absuri = u.String() | ||||
| 		u.Scheme = "" | ||||
| 		u.Host = "" | ||||
| 		req.ruri = u.String() | ||||
| 	} else { | ||||
| 		req.ruri = u.String() | ||||
| 
 | ||||
| 		base, err := url.Parse("http://" + header.Get("Host")) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		u = base.ResolveReference(u) | ||||
| 		req.absuri = u.String() | ||||
| 	} | ||||
| 
 | ||||
| 	return req, nil | ||||
| } | ||||
| 
 | ||||
| func (r *request) Marshal() []byte { | ||||
| 	var buf bytes.Buffer | ||||
| 	writeStartLine(&buf, r.method, r.ruri, r.proto) | ||||
| 	writeHeaders(&buf, r.header) | ||||
| 	return buf.Bytes() | ||||
| } | ||||
| 
 | ||||
| func (r *request) MarshalAbs() []byte { | ||||
| 	var buf bytes.Buffer | ||||
| 	writeStartLine(&buf, r.method, r.absuri, r.proto) | ||||
| 	writeHeaders(&buf, r.header) | ||||
| 	return buf.Bytes() | ||||
| } | ||||
							
								
								
									
										163
									
								
								proxy/http/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								proxy/http/server.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,163 @@ | ||||
| package http | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/textproto" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/nadoo/glider/common/conn" | ||||
| 	"github.com/nadoo/glider/common/log" | ||||
| 	"github.com/nadoo/glider/proxy" | ||||
| ) | ||||
| 
 | ||||
| // NewHTTPServer returns a http proxy server.
 | ||||
| func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) { | ||||
| 	return NewHTTP(s, nil, p) | ||||
| } | ||||
| 
 | ||||
| // ListenAndServe listens on server's addr and serves connections.
 | ||||
| func (s *HTTP) ListenAndServe() { | ||||
| 	l, err := net.Listen("tcp", s.addr) | ||||
| 	if err != nil { | ||||
| 		log.F("[http] failed to listen on %s: %v", s.addr, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer l.Close() | ||||
| 
 | ||||
| 	log.F("[http] listening TCP on %s", s.addr) | ||||
| 
 | ||||
| 	for { | ||||
| 		c, err := l.Accept() | ||||
| 		if err != nil { | ||||
| 			log.F("[http] failed to accept: %v", err) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		go s.Serve(c) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Serve serves a connection.
 | ||||
| func (s *HTTP) Serve(cc net.Conn) { | ||||
| 	defer cc.Close() | ||||
| 
 | ||||
| 	var c *conn.Conn | ||||
| 	switch ccc := cc.(type) { | ||||
| 	case *net.TCPConn: | ||||
| 		ccc.SetKeepAlive(true) | ||||
| 		c = conn.NewConn(ccc) | ||||
| 	case *conn.Conn: | ||||
| 		c = ccc | ||||
| 	} | ||||
| 
 | ||||
| 	req, err := parseRequest(c.Reader()) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if s.pretend { | ||||
| 		fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", req.proto) | ||||
| 		log.F("[http] accessed by %s as web server", c.RemoteAddr().String()) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	s.servRequest(req, c) | ||||
| } | ||||
| 
 | ||||
| func (s *HTTP) servRequest(req *request, c *conn.Conn) { | ||||
| 	// Auth
 | ||||
| 	if s.user != "" && s.password != "" { | ||||
| 		if user, pass, ok := extractUserPass(req.auth); !ok || user != s.user || pass != s.password { | ||||
| 			c.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) | ||||
| 			log.F("[http] auth failed from %s, auth info: %s:%s", c.RemoteAddr(), user, pass) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if req.method == "CONNECT" { | ||||
| 		s.servHTTPS(req, c) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	s.servHTTP(req, c) | ||||
| } | ||||
| 
 | ||||
| func (s *HTTP) servHTTPS(r *request, c net.Conn) { | ||||
| 	rc, p, err := s.proxy.Dial("tcp", r.uri) | ||||
| 	if err != nil { | ||||
| 		c.Write([]byte(r.proto + " 502 ERROR\r\n\r\n")) | ||||
| 		log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), r.uri, p, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) | ||||
| 
 | ||||
| 	log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), r.uri, p) | ||||
| 
 | ||||
| 	_, _, err = conn.Relay(c, rc) | ||||
| 	if err != nil { | ||||
| 		if err, ok := err.(net.Error); ok && err.Timeout() { | ||||
| 			return // ignore i/o timeout
 | ||||
| 		} | ||||
| 		log.F("[http] relay error: %v", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *HTTP) servHTTP(req *request, c *conn.Conn) { | ||||
| 	rc, p, err := s.proxy.Dial("tcp", req.target) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", req.proto) | ||||
| 		log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), req.target, p, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer rc.Close() | ||||
| 
 | ||||
| 	// send request to remote server
 | ||||
| 	_, err = rc.Write(req.Marshal()) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// copy the left request bytes to remote server. eg. length specificed or chunked body.
 | ||||
| 	go func() { | ||||
| 		if _, err := c.Reader().Peek(1); err == nil { | ||||
| 			io.Copy(rc, c) | ||||
| 			rc.SetDeadline(time.Now()) | ||||
| 			c.SetDeadline(time.Now()) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	r := bufio.NewReader(rc) | ||||
| 	tpr := textproto.NewReader(r) | ||||
| 	line, err := tpr.ReadLine() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	proto, code, status, ok := parseStartLine(line) | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	header, err := tpr.ReadMIMEHeader() | ||||
| 	if err != nil { | ||||
| 		log.F("[http] read header error:%s", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	header.Set("Proxy-Connection", "close") | ||||
| 	header.Set("Connection", "close") | ||||
| 
 | ||||
| 	var buf bytes.Buffer | ||||
| 	writeStartLine(&buf, proto, code, status) | ||||
| 	writeHeaders(&buf, header) | ||||
| 
 | ||||
| 	log.F("[http] %s <-> %s", c.RemoteAddr(), req.target) | ||||
| 	c.Write(buf.Bytes()) | ||||
| 
 | ||||
| 	io.Copy(c, r) | ||||
| } | ||||
| @ -12,26 +12,13 @@ import ( | ||||
| 	"github.com/nadoo/glider/proxy/socks5" | ||||
| ) | ||||
| 
 | ||||
| // https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase
 | ||||
| var httpMethods = [...][]byte{ | ||||
| 	[]byte("GET"), | ||||
| 	[]byte("POST"), | ||||
| 	[]byte("PUT"), | ||||
| 	[]byte("DELETE"), | ||||
| 	[]byte("CONNECT"), | ||||
| 	[]byte("HEAD"), | ||||
| 	[]byte("OPTIONS"), | ||||
| 	[]byte("TRACE"), | ||||
| 	[]byte("PATCH"), | ||||
| } | ||||
| 
 | ||||
| // Mixed struct.
 | ||||
| type Mixed struct { | ||||
| 	proxy proxy.Proxy | ||||
| 	addr  string | ||||
| 
 | ||||
| 	http   *http.HTTP | ||||
| 	socks5 *socks5.Socks5 | ||||
| 	httpServer   *http.HTTP | ||||
| 	socks5Server *socks5.Socks5 | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| @ -51,8 +38,15 @@ func NewMixed(s string, p proxy.Proxy) (*Mixed, error) { | ||||
| 		addr:  u.Host, | ||||
| 	} | ||||
| 
 | ||||
| 	m.http, _ = http.NewHTTP(s, nil, p) | ||||
| 	m.socks5, _ = socks5.NewSocks5(s, nil, p) | ||||
| 	m.httpServer, err = http.NewHTTP(s, nil, p) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	m.socks5Server, err = socks5.NewSocks5(s, nil, p) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return m, nil | ||||
| } | ||||
| @ -64,7 +58,7 @@ func NewMixedServer(s string, p proxy.Proxy) (proxy.Server, error) { | ||||
| 
 | ||||
| // ListenAndServe listens on server's addr and serves connections.
 | ||||
| func (m *Mixed) ListenAndServe() { | ||||
| 	go m.socks5.ListenAndServeUDP() | ||||
| 	go m.socks5Server.ListenAndServeUDP() | ||||
| 
 | ||||
| 	l, err := net.Listen("tcp", m.addr) | ||||
| 	if err != nil { | ||||
| @ -95,32 +89,28 @@ func (m *Mixed) Serve(c net.Conn) { | ||||
| 
 | ||||
| 	cc := conn.NewConn(c) | ||||
| 
 | ||||
| 	if m.socks5 != nil { | ||||
| 		head, err := cc.Peek(1) | ||||
| 		if err != nil { | ||||
| 			// log.F("[mixed] socks5 peek error: %s", err)
 | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		// check socks5, client send socksversion: 5 as the first byte
 | ||||
| 		if head[0] == socks5.Version { | ||||
| 			m.socks5.Serve(cc) | ||||
| 			return | ||||
| 		} | ||||
| 	head, err := cc.Peek(1) | ||||
| 	if err != nil { | ||||
| 		// log.F("[mixed] socks5 peek error: %s", err)
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if m.http != nil { | ||||
| 		head, err := cc.Peek(8) | ||||
| 		if err != nil { | ||||
| 			log.F("[mixed] http peek error: %s", err) | ||||
| 			return | ||||
| 		} | ||||
| 	// check socks5, client send socksversion: 5 as the first byte
 | ||||
| 	if head[0] == socks5.Version { | ||||
| 		m.socks5Server.Serve(cc) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 		for _, method := range httpMethods { | ||||
| 			if bytes.HasPrefix(head, method) { | ||||
| 				m.http.Serve(cc) | ||||
| 				return | ||||
| 			} | ||||
| 	head, err = cc.Peek(8) | ||||
| 	if err != nil { | ||||
| 		log.F("[mixed] http peek error: %s", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	for _, method := range http.Methods { | ||||
| 		if bytes.HasPrefix(head, method) { | ||||
| 			m.httpServer.Serve(cc) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -103,11 +103,16 @@ func (s *Socks5) ListenAndServeTCP() { | ||||
| } | ||||
| 
 | ||||
| // Serve serves a connection.
 | ||||
| func (s *Socks5) Serve(c net.Conn) { | ||||
| 	defer c.Close() | ||||
| func (s *Socks5) Serve(cc net.Conn) { | ||||
| 	defer cc.Close() | ||||
| 
 | ||||
| 	if c, ok := c.(*net.TCPConn); ok { | ||||
| 		c.SetKeepAlive(true) | ||||
| 	var c *conn.Conn | ||||
| 	switch ccc := cc.(type) { | ||||
| 	case *net.TCPConn: | ||||
| 		ccc.SetKeepAlive(true) | ||||
| 		c = conn.NewConn(ccc) | ||||
| 	case *conn.Conn: | ||||
| 		c = ccc | ||||
| 	} | ||||
| 
 | ||||
| 	tgt, err := s.handshake(c) | ||||
| @ -126,7 +131,7 @@ func (s *Socks5) Serve(c net.Conn) { | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		log.F("[socks5] failed in handshake: %v", err) | ||||
| 		log.F("[socks5] failed in handshake with %s: %v", c.RemoteAddr(), err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| @ -483,7 +488,7 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) { | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			return nil, errors.New("auth failed") | ||||
| 			return nil, errors.New("auth failed, authinfo: " + user + ":" + pass) | ||||
| 		} | ||||
| 
 | ||||
| 		// Response auth state
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 nadoo
						nadoo