Browse Source

Feature: HTTP proxy support

pull/76/head
xjasonlyu 3 years ago
parent
commit
ab2f4043f7
  1. 6
      engine/parse.go
  2. 97
      proxy/http.go
  3. 9
      proxy/proto/proto.go

6
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()

97
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)
}

9
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)
}

Loading…
Cancel
Save