From 821cbc302e2eb68e341cc5e04d011ffd71aae238 Mon Sep 17 00:00:00 2001 From: wweir Date: Wed, 16 Jan 2019 18:42:03 +0800 Subject: [PATCH] Add http proxy support --- README.md | 8 ++-- conf/conf.go | 18 +++++---- conf/sower.toml | 4 +- main.go | 8 +++- proxy/client.go | 17 +++++---- proxy/http_proxy.go | 91 +++++++++++++++++++++++++++++++++++++++++++++ proxy/kcp/server.go | 2 +- proxy/server.go | 15 +++++--- 8 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 proxy/http_proxy.go diff --git a/README.md b/README.md index 8161ee0..f18e3e4 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,10 @@ Yet another cross platform transparent proxy tool | | | | 127.0.0.1 or other | | | | - +-------^----------+---^----^---+ - | | | - | | | +-----> - | +----------+ | | + +-^-----^----------+---^----^---+ + | | | | + | | | | +-----> +http(s) proxy | +----------+ | | 2 1 1 2 + + + + blocked request normal request diff --git a/conf/conf.go b/conf/conf.go index 563cfb8..1035166 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -17,16 +17,17 @@ import ( var Conf = struct { ConfigFile string NetType string `toml:"net_type"` - Cipher string `toml:cipher` + Cipher string `toml:"cipher"` Password string `toml:"password"` - ServerPort string `toml:"server_port"` - ServerAddr string `toml:"server_addr"` + ServerPort string `toml:"server_port"` + ServerAddr string `toml:"server_addr"` + HTTPProxyPort string `toml:"http_proxy_port"` - DnsServer string `toml:"dns_server"` + DNSServer string `toml:"dns_server"` ClientIP string `toml:"client_ip"` ClientIPNet net.IP `toml:"-"` - ClearDnsCache string `toml:"clear_dns_cache"` + ClearDNSCache string `toml:"clear_dns_cache"` BlockList []string `toml:"blocklist"` Suggestions []string `toml:"suggestions"` @@ -40,7 +41,8 @@ func init() { flag.StringVar(&Conf.Password, "p", "12345678", "password") flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port") flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)") - flag.StringVar(&Conf.DnsServer, "d", "114.114.114.114", "client dns server") + flag.StringVar(&Conf.HTTPProxyPort, "H", "", "http proxy listen port") + flag.StringVar(&Conf.DNSServer, "d", "114.114.114.114", "client dns server") flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP") if !flag.Parsed() { @@ -63,9 +65,9 @@ var OnRefreash = []func() error{func() error { Conf.ClientIPNet = net.ParseIP(Conf.ClientIP) // clear dns cache - if Conf.ClearDnsCache != "" { + if Conf.ClearDNSCache != "" { ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second) - if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDnsCache).Run(); err != nil { + if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run(); err != nil { glog.Errorln(err) } } diff --git a/conf/sower.toml b/conf/sower.toml index 025d7a9..1283b65 100644 --- a/conf/sower.toml +++ b/conf/sower.toml @@ -3,6 +3,7 @@ cipher="AES_128_GCM" password="12345678" server_port="5533" server_addr="remote-server:5533" # replce it to remote server +http_proxy_port="8080" dns_server="223.5.5.5" # Alibaba public dns client_ip="127.0.0.1" # clear_dns_cache="killall -HUP mDNSResponder" @@ -44,6 +45,7 @@ blocklist=[ "synchrony-cdn.atlassian.com", "avatar-cdn.atlassian.com", "*.medium.com", + "*.us-east-1.prod.public.atl-paas.net", "accounts-static.cdn.mozilla.net", # firefox "*.services.mozilla.com", "pocket-image-cache.com", # pocket @@ -73,8 +75,6 @@ blocklist=[ "download.apkpure.com", "*.nytimes.com", # New York times "*.nyt.com", - "bandwagonhost.com", # bandwagonhost - "www.bwh1.net", "*.akadns.net", # suggestions "*.haxx.se", "shadowsocks.org", diff --git a/main.go b/main.go index f7236bb..1012dec 100644 --- a/main.go +++ b/main.go @@ -13,8 +13,14 @@ func main() { if conf.ServerAddr == "" { proxy.StartServer(conf.NetType, conf.ServerPort, conf.Cipher, conf.Password) + } else { - go dns.StartDNS(conf.DnsServer, conf.ClientIP, conf.ClientIPNet) + if conf.HTTPProxyPort != "" { + go proxy.StartHttpProxy(conf.NetType, conf.ServerAddr, + conf.Cipher, conf.Password, conf.ClientIP, conf.HTTPProxyPort) + } + + go dns.StartDNS(conf.DNSServer, conf.ClientIP, conf.ClientIPNet) proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP) } } diff --git a/proxy/client.go b/proxy/client.go index 84824b2..9f1ab24 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -14,20 +14,23 @@ type Client interface { Dial(server string) (net.Conn, error) } -func StartClient(netType, server, cipher, password, listenIP string) { - var connCh = listenLocal(listenIP, []string{":80", ":443"}) - - var client Client +func NewClient(netType string) Client { switch netType { case QUIC.String(): - client = quic.NewClient() + return quic.NewClient() case KCP.String(): - client = kcp.NewClient() + return kcp.NewClient() case TCP.String(): - client = tcp.NewClient() + return tcp.NewClient() default: glog.Fatalln("invalid net type: " + netType) + return nil } +} + +func StartClient(netType, server, cipher, password, listenIP string) { + connCh := listenLocal(listenIP, []string{":80", ":443"}) + client := NewClient(netType) glog.Infoln("Client started.") for { diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go new file mode 100644 index 0000000..a0ce912 --- /dev/null +++ b/proxy/http_proxy.go @@ -0,0 +1,91 @@ +package proxy + +import ( + "context" + "crypto/tls" + "io" + "net" + "net/http" + "time" + + "github.com/golang/glog" + "github.com/wweir/sower/shadow" +) + +func StartHttpProxy(netType, server, cipher, password, listenIP, port string) { + if port[0] != ':' { + port = ":" + port + } + + client := NewClient(netType) + + srv := &http.Server{ + Addr: listenIP + port, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodConnect { + httpsProxy(w, r, client, server, cipher, password) + } else { + httpProxy(w, r, client, server, cipher, password) + } + }), + // Disable HTTP/2. + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } + + glog.Fatalln(srv.ListenAndServe()) +} + +func httpsProxy(w http.ResponseWriter, r *http.Request, client Client, server, cipher, password string) { + // remote conn + rc, err := client.Dial(server) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + rc = shadow.Shadow(rc, cipher, password) + + // local conn + w.WriteHeader(http.StatusOK) + hijacker, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Hijacking not supported", http.StatusInternalServerError) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + } + + relay(rc, conn) +} + +func httpProxy(w http.ResponseWriter, req *http.Request, client Client, server, cipher, password string) { + roundTripper := &http.Transport{ + DialContext: func(context.Context, string, string) (net.Conn, error) { + conn, err := client.Dial(server) + if err != nil { + return nil, err + } + return shadow.Shadow(conn, cipher, password), nil + }, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + resp, err := roundTripper.RoundTrip(req) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer resp.Body.Close() + + for k, vs := range resp.Header { + for _, v := range vs { + w.Header().Add(k, v) + } + } + w.WriteHeader(resp.StatusCode) + io.Copy(w, resp.Body) +} diff --git a/proxy/kcp/server.go b/proxy/kcp/server.go index 4156cd0..91a0b50 100644 --- a/proxy/kcp/server.go +++ b/proxy/kcp/server.go @@ -15,7 +15,7 @@ type server struct { SockBuf int } -func NewServer(password string) *server { +func NewServer() *server { return &server{ DataShard: 10, ParityShard: 3, diff --git a/proxy/server.go b/proxy/server.go index 891c9ec..c60a668 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -16,26 +16,29 @@ type Server interface { Listen(port string) (<-chan net.Conn, error) } -func StartServer(netType, port, cipher, password string) { - var server Server +func NewServer(netType string) Server { switch netType { case QUIC.String(): - server = quic.NewServer() + return quic.NewServer() case KCP.String(): - server = kcp.NewServer(password) + return kcp.NewServer() case TCP.String(): - server = tcp.NewServer() + return tcp.NewServer() default: glog.Fatalln("invalid net type: " + netType) + return nil } +} +func StartServer(netType, port, cipher, password string) { if port == "" { glog.Fatalln("port must set") } if !strings.Contains(port, ":") { port = ":" + port } - connCh, err := server.Listen(port) + + connCh, err := NewServer(netType).Listen(port) if err != nil { glog.Fatalf("listen %v fail: %s", port, err) }