mirror of
https://github.com/nadoo/glider.git
synced 2025-02-24 01:45:39 +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
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
glider 0.8.0 usage:
|
glider 0.9.0 usage:
|
||||||
-checkinterval int
|
-checkinterval int
|
||||||
proxy check interval(seconds) (default 30)
|
proxy check interval(seconds) (default 30)
|
||||||
-checktimeout int
|
-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
|
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:
|
Alias:
|
||||||
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
||||||
|
Plain: DUMMY
|
||||||
|
|
||||||
SSR scheme:
|
SSR scheme:
|
||||||
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||||
@ -282,17 +283,14 @@ Examples:
|
|||||||
glider -config glider.conf
|
glider -config glider.conf
|
||||||
-run glider with specified config file.
|
-run glider with specified config file.
|
||||||
|
|
||||||
glider -config glider.conf -rulefile office.rule -rulefile home.rule
|
glider -listen :8443 -verbose
|
||||||
-run glider with specified global config file and rule config files.
|
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
||||||
|
|
||||||
glider -listen :8443
|
glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose
|
||||||
-listen on :8443, serve as http/socks5 proxy on the same port.
|
|
||||||
|
|
||||||
glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443
|
|
||||||
-listen on 0.0.0.0:8443 as a ss server.
|
-listen on 0.0.0.0:8443 as a ss server.
|
||||||
|
|
||||||
glider -listen socks5://:1080 -verbose
|
glider -listen socks5://user1:pass1@:1080 -verbose
|
||||||
-listen on :1080 as a socks5 proxy server, in verbose mode.
|
-listen on :1080 as a socks5 proxy server, enable authentication.
|
||||||
|
|
||||||
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
||||||
-listen on :443 as a https(http over tls) proxy server.
|
-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, " "+app+" -config glider.conf\n")
|
||||||
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
||||||
fmt.Fprintf(os.Stderr, "\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, " "+app+" -listen :8443 -verbose\n")
|
||||||
fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\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, "\n")
|
||||||
fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\n")
|
fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose\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, " -listen on 0.0.0.0:8443 as a ss server.\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, "\n")
|
||||||
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\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, in verbose mode.\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, "\n")
|
||||||
fmt.Fprintf(os.Stderr, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\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")
|
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,19 +5,12 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/common/conn"
|
|
||||||
"github.com/nadoo/glider/common/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
@ -29,7 +22,7 @@ type HTTP struct {
|
|||||||
addr string
|
addr string
|
||||||
user string
|
user string
|
||||||
password string
|
password string
|
||||||
pretendAsWebServer bool
|
pretend bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -55,235 +48,18 @@ func NewHTTP(s string, d proxy.Dialer, p proxy.Proxy) (*HTTP, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
user: user,
|
user: user,
|
||||||
password: pass,
|
password: pass,
|
||||||
pretendAsWebServer: false,
|
pretend: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
pretend := u.Query().Get("pretend")
|
if u.Query().Get("pretend") == "true" {
|
||||||
if pretend == "true" {
|
h.pretend = true
|
||||||
h.pretendAsWebServer = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return h, nil
|
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.
|
// 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) {
|
func parseStartLine(line string) (r1, r2, r3 string, ok bool) {
|
||||||
line, err := tp.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s1 := strings.Index(line, " ")
|
s1 := strings.Index(line, " ")
|
||||||
s2 := strings.Index(line[s1+1:], " ")
|
s2 := strings.Index(line[s1+1:], " ")
|
||||||
if s1 < 0 || s2 < 0 {
|
if s1 < 0 || s2 < 0 {
|
||||||
@ -317,3 +93,22 @@ func writeHeaders(buf *bytes.Buffer, header textproto.MIMEHeader) {
|
|||||||
}
|
}
|
||||||
buf.WriteString("\r\n")
|
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"
|
"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.
|
// Mixed struct.
|
||||||
type Mixed struct {
|
type Mixed struct {
|
||||||
proxy proxy.Proxy
|
proxy proxy.Proxy
|
||||||
addr string
|
addr string
|
||||||
|
|
||||||
http *http.HTTP
|
httpServer *http.HTTP
|
||||||
socks5 *socks5.Socks5
|
socks5Server *socks5.Socks5
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -51,8 +38,15 @@ func NewMixed(s string, p proxy.Proxy) (*Mixed, error) {
|
|||||||
addr: u.Host,
|
addr: u.Host,
|
||||||
}
|
}
|
||||||
|
|
||||||
m.http, _ = http.NewHTTP(s, nil, p)
|
m.httpServer, err = http.NewHTTP(s, nil, p)
|
||||||
m.socks5, _ = socks5.NewSocks5(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
|
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.
|
// ListenAndServe listens on server's addr and serves connections.
|
||||||
func (m *Mixed) ListenAndServe() {
|
func (m *Mixed) ListenAndServe() {
|
||||||
go m.socks5.ListenAndServeUDP()
|
go m.socks5Server.ListenAndServeUDP()
|
||||||
|
|
||||||
l, err := net.Listen("tcp", m.addr)
|
l, err := net.Listen("tcp", m.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,7 +89,6 @@ func (m *Mixed) Serve(c net.Conn) {
|
|||||||
|
|
||||||
cc := conn.NewConn(c)
|
cc := conn.NewConn(c)
|
||||||
|
|
||||||
if m.socks5 != nil {
|
|
||||||
head, err := cc.Peek(1)
|
head, err := cc.Peek(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log.F("[mixed] socks5 peek error: %s", err)
|
// log.F("[mixed] socks5 peek error: %s", err)
|
||||||
@ -104,24 +97,21 @@ func (m *Mixed) Serve(c net.Conn) {
|
|||||||
|
|
||||||
// check socks5, client send socksversion: 5 as the first byte
|
// check socks5, client send socksversion: 5 as the first byte
|
||||||
if head[0] == socks5.Version {
|
if head[0] == socks5.Version {
|
||||||
m.socks5.Serve(cc)
|
m.socks5Server.Serve(cc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if m.http != nil {
|
head, err = cc.Peek(8)
|
||||||
head, err := cc.Peek(8)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[mixed] http peek error: %s", err)
|
log.F("[mixed] http peek error: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, method := range httpMethods {
|
for _, method := range http.Methods {
|
||||||
if bytes.HasPrefix(head, method) {
|
if bytes.HasPrefix(head, method) {
|
||||||
m.http.Serve(cc)
|
m.httpServer.Serve(cc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -103,11 +103,16 @@ func (s *Socks5) ListenAndServeTCP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Serve serves a connection.
|
// Serve serves a connection.
|
||||||
func (s *Socks5) Serve(c net.Conn) {
|
func (s *Socks5) Serve(cc net.Conn) {
|
||||||
defer c.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
if c, ok := c.(*net.TCPConn); ok {
|
var c *conn.Conn
|
||||||
c.SetKeepAlive(true)
|
switch ccc := cc.(type) {
|
||||||
|
case *net.TCPConn:
|
||||||
|
ccc.SetKeepAlive(true)
|
||||||
|
c = conn.NewConn(ccc)
|
||||||
|
case *conn.Conn:
|
||||||
|
c = ccc
|
||||||
}
|
}
|
||||||
|
|
||||||
tgt, err := s.handshake(c)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,7 +488,7 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, errors.New("auth failed")
|
return nil, errors.New("auth failed, authinfo: " + user + ":" + pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response auth state
|
// Response auth state
|
||||||
|
Loading…
Reference in New Issue
Block a user