diff --git a/engine/parse.go b/engine/parse.go index a86fd9a..cef14a6 100644 --- a/engine/parse.go +++ b/engine/parse.go @@ -50,8 +50,10 @@ func parseProxy(s string) (proxy.Proxy, error) { return proxy.NewDirect(), nil case proto.Reject.String(): return proxy.NewReject(), nil + case proto.HTTP.String(): + return proxy.NewHTTP(parseAddrUser(u)) case proto.Socks5.String(): - return proxy.NewSocks5(parseSocks(u)) + return proxy.NewSocks5(parseAddrUser(u)) case proto.Shadowsocks.String(): return proxy.NewShadowsocks(parseShadowsocks(u)) default: @@ -59,7 +61,7 @@ func parseProxy(s string) (proxy.Proxy, error) { } } -func parseSocks(u *url.URL) (address, username, password string) { +func parseAddrUser(u *url.URL) (address, username, password string) { address = u.Host username = u.User.Username() password, _ = u.User.Password() diff --git a/proxy/http.go b/proxy/http.go new file mode 100644 index 0000000..e7fc63e --- /dev/null +++ b/proxy/http.go @@ -0,0 +1,97 @@ +package proxy + +// Ref: https://github.com/Dreamacro/clash/adapter/outbound/http + +import ( + "bufio" + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + + "github.com/xjasonlyu/tun2socks/component/dialer" + M "github.com/xjasonlyu/tun2socks/constant" + "github.com/xjasonlyu/tun2socks/proxy/proto" +) + +type HTTP struct { + *Base + + user string + pass string +} + +func NewHTTP(addr, user, pass string) (*HTTP, error) { + return &HTTP{ + Base: &Base{ + addr: addr, + proto: proto.HTTP, + }, + user: user, + pass: pass, + }, nil +} + +func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { + c, err = dialer.DialContext(ctx, "tcp", h.Addr()) + if err != nil { + return nil, fmt.Errorf("connect to %s: %w", h.Addr(), err) + } + setKeepAlive(c) + + defer safeConnClose(c, err) + + err = h.shakeHand(metadata, c) + return +} + +func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error { + addr := metadata.DestinationAddress() + req := &http.Request{ + Method: http.MethodConnect, + URL: &url.URL{ + Host: addr, + }, + Host: addr, + Header: http.Header{ + "Proxy-Connection": []string{"Keep-Alive"}, + }, + } + + if h.user != "" && h.pass != "" { + auth := h.user + ":" + h.pass + req.Header.Add("Proxy-Authorization", + fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth)))) + } + + if err := req.Write(rw); err != nil { + return err + } + + resp, err := http.ReadResponse(bufio.NewReader(rw), req) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusOK { + return nil + } + + if resp.StatusCode == http.StatusProxyAuthRequired { + return errors.New("HTTP need auth") + } + + if resp.StatusCode == http.StatusMethodNotAllowed { + return errors.New("CONNECT method not allowed by proxy") + } + + if resp.StatusCode >= http.StatusInternalServerError { + return errors.New(resp.Status) + } + + return fmt.Errorf("HTTP connect status code: %d", resp.StatusCode) +} diff --git a/proxy/proto/proto.go b/proxy/proto/proto.go index 07770e7..dad46e9 100644 --- a/proxy/proto/proto.go +++ b/proxy/proto/proto.go @@ -5,8 +5,9 @@ import "fmt" const ( Direct Proto = iota Reject - Shadowsocks + HTTP Socks5 + Shadowsocks ) type Proto uint8 @@ -17,10 +18,12 @@ func (proto Proto) String() string { return "direct" case Reject: return "reject" - case Shadowsocks: - return "ss" + case HTTP: + return "http" case Socks5: return "socks5" + case Shadowsocks: + return "ss" default: return fmt.Sprintf("proto(%d)", proto) }