fix http proxy

This commit is contained in:
rui.zheng 2015-10-10 18:08:58 +08:00
parent 037a79a00d
commit e19ff132f4
6 changed files with 266 additions and 53 deletions

View File

179
conn.go
View File

@ -2,18 +2,47 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"crypto/tls" "crypto/tls"
"encoding/base64"
"errors"
"github.com/ginuerzh/gosocks5" "github.com/ginuerzh/gosocks5"
"github.com/golang/glog" "github.com/golang/glog"
"io" "io"
"net" "net"
"net/http" "net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
)
const (
ConnHttp = "http"
ConnHttpConnect = "http-connect"
ConnSocks5 = "socks5"
) )
func listenAndServe(arg Args) error { func listenAndServe(arg Args) error {
var ln net.Listener var ln net.Listener
var err error var err error
if glog.V(3) {
b := bytes.Buffer{}
b.WriteString("listen on %s, use %s tunnel and %s protocol for data transport. ")
if arg.EncMeth == "tls" {
b.WriteString("for socks5, tls encrypt method is supported.")
} else {
b.WriteString("for socks5, tls encrypt method is NOT supported.")
}
protocol := arg.Protocol
if protocol == "" {
protocol = "http/socks5"
}
glog.Infof(b.String(), arg.Addr, arg.Transport, protocol)
}
switch arg.Transport { switch arg.Transport {
case "ws": // websocket connection case "ws": // websocket connection
err = NewWs(arg).ListenAndServe() err = NewWs(arg).ListenAndServe()
@ -26,6 +55,8 @@ func listenAndServe(arg Args) error {
case "tls": // tls connection case "tls": // tls connection
ln, err = tls.Listen("tcp", arg.Addr, ln, err = tls.Listen("tcp", arg.Addr,
&tls.Config{Certificates: []tls.Certificate{arg.Cert}}) &tls.Config{Certificates: []tls.Certificate{arg.Cert}})
case "tcp":
fallthrough
default: default:
ln, err = net.Listen("tcp", arg.Addr) ln, err = net.Listen("tcp", arg.Addr)
} }
@ -36,6 +67,7 @@ func listenAndServe(arg Args) error {
} }
return err return err
} }
defer ln.Close() defer ln.Close()
for { for {
@ -61,11 +93,14 @@ func handleConn(conn net.Conn, arg Args) {
selector := &serverSelector{ selector := &serverSelector{
methods: []uint8{ methods: []uint8{
gosocks5.MethodNoAuth, gosocks5.MethodUserPass, gosocks5.MethodNoAuth, gosocks5.MethodUserPass,
MethodTLS, MethodTLSAuth,
}, },
arg: arg, arg: arg,
} }
if arg.EncMeth == "tls" {
selector.methods = append(selector.methods, MethodTLS, MethodTLSAuth)
}
switch arg.Protocol { switch arg.Protocol {
case "ss": // shadowsocks case "ss": // shadowsocks
return return
@ -174,3 +209,145 @@ func (r *reqReader) Read(p []byte) (n int, err error) {
return return
} }
func connect(connType, addr string) (conn net.Conn, err error) {
if !strings.Contains(addr, ":") {
addr += ":80"
}
if len(forwardArgs) > 0 {
// TODO: multi-foward
forward := forwardArgs[0]
return connectForward(addr, forward)
}
if len(proxyArgs) > 0 {
proxy := proxyArgs[0]
return connectProxy(connType, addr, proxy)
}
return net.Dial("tcp", addr)
}
func connectProxy(connType, addr string, proxy Args) (conn net.Conn, err error) {
if glog.V(LINFO) {
glog.Infoln("connect proxy:", proxy.Addr)
}
conn, err = net.Dial("tcp", proxy.Addr)
if err != nil {
return
}
switch proxy.Transport {
case "ws": // websocket connection
c, err := wsClient(conn, proxy.Addr)
if err != nil {
conn.Close()
return nil, err
}
conn = c
case "tls": // tls connection
conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
case "tcp":
fallthrough
default:
}
switch proxy.Protocol {
case "ss": // shadowsocks
conn.Close()
return nil, errors.New("Not implemented")
case "socks", "socks5":
selector := &clientSelector{
methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass},
arg: proxy,
}
if proxy.EncMeth == "tls" {
selector.methods = []uint8{MethodTLS, MethodTLSAuth}
}
c := gosocks5.ClientConn(conn, selector)
if err := c.Handleshake(); err != nil {
c.Close()
return nil, err
}
conn = c
if connType == ConnHttp || connType == ConnHttpConnect {
host, port, _ := net.SplitHostPort(addr)
p, _ := strconv.ParseUint(port, 10, 16)
r := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
Type: gosocks5.AddrDomain,
Host: host,
Port: uint16(p),
})
if glog.V(LDEBUG) {
glog.Infoln(r.String())
}
if err := r.Write(conn); err != nil {
conn.Close()
return nil, err
}
rep, err := gosocks5.ReadReply(conn)
if err != nil {
conn.Close()
return nil, err
}
if glog.V(LDEBUG) {
glog.Infoln(rep.String())
}
if rep.Rep != gosocks5.Succeeded {
conn.Close()
return nil, errors.New("Service unavailable")
}
}
case "http":
fallthrough
default:
if connType == ConnHttpConnect {
req := &http.Request{
Method: "CONNECT",
URL: &url.URL{Host: addr},
Host: addr,
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
}
req.Header.Set("Proxy-Connection", "keep-alive")
if proxy.User != nil {
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(proxy.User.String())))
}
if err = req.Write(conn); err != nil {
conn.Close()
return nil, err
}
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
conn.Close()
return nil, err
}
if glog.V(LDEBUG) {
dump, _ := httputil.DumpResponse(resp, false)
glog.Infoln(string(dump))
}
if resp.StatusCode != http.StatusOK {
conn.Close()
//log.Println(resp.Status)
return nil, errors.New(resp.Status)
}
}
}
return
}
func connectForward(addr string, forward Args) (net.Conn, error) {
return nil, errors.New("Not implemented")
}

52
http.go
View File

@ -19,13 +19,18 @@ func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) {
} }
} }
connType := ConnHttp
if req.Method == "CONNECT" {
connType = ConnHttpConnect
}
var username, password string var username, password string
if arg.User != nil { if arg.User != nil {
username = arg.User.Username() username = arg.User.Username()
password, _ = arg.User.Password() password, _ = arg.User.Password()
} }
u, p, _ := proxyBasicAuth(req.Header.Get("Proxy-Authorization")) u, p, _ := basicAuth(req.Header.Get("Proxy-Authorization"))
req.Header.Del("Proxy-Authorization") req.Header.Del("Proxy-Authorization")
if (username != "" && u != username) || (password != "" && p != password) { if (username != "" && u != username) || (password != "" && p != password) {
@ -46,9 +51,52 @@ func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) {
} }
return return
} }
c, err := connect(connType, req.Host)
if err != nil {
if glog.V(LWARNING) {
glog.Warningln(err)
}
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
if glog.V(LDEBUG) {
glog.Infoln(string(b))
}
conn.Write(b)
return
}
defer c.Close()
if connType == ConnHttpConnect {
b := []byte("HTTP/1.1 200 Connection established\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
if glog.V(LDEBUG) {
glog.Infoln(string(b))
}
if _, err := conn.Write(b); err != nil {
if glog.V(LWARNING) {
glog.Warningln(err)
}
return
}
} else {
if len(proxyArgs) > 0 {
err = req.WriteProxy(c)
} else {
err = req.Write(c)
}
if err != nil {
if glog.V(LWARNING) {
glog.Warningln(err)
}
return
}
}
Transport(conn, c)
} }
func proxyBasicAuth(authInfo string) (username, password string, ok bool) { func basicAuth(authInfo string) (username, password string, ok bool) {
if authInfo == "" { if authInfo == "" {
return return
} }

20
main.go
View File

@ -17,6 +17,7 @@ const (
var ( var (
listenUrl, proxyUrl, forwardUrl string listenUrl, proxyUrl, forwardUrl string
pv bool // print version
listenArgs []Args listenArgs []Args
proxyArgs []Args proxyArgs []Args
@ -27,6 +28,7 @@ func init() {
flag.StringVar(&listenUrl, "L", ":http", "local address") flag.StringVar(&listenUrl, "L", ":http", "local address")
flag.StringVar(&forwardUrl, "S", "", "remote address") flag.StringVar(&forwardUrl, "S", "", "remote address")
flag.StringVar(&proxyUrl, "P", "", "proxy address") flag.StringVar(&proxyUrl, "P", "", "proxy address")
flag.BoolVar(&pv, "V", false, "print version")
flag.Parse() flag.Parse()
@ -38,22 +40,22 @@ func init() {
func main() { func main() {
defer glog.Flush() defer glog.Flush()
if pv {
printVersion()
return
}
if len(listenArgs) == 0 { if len(listenArgs) == 0 {
glog.Fatalln("no listen addr") glog.Fatalln("no listen addr")
} }
var wg sync.WaitGroup var wg sync.WaitGroup
for _, args := range listenArgs {
for _, arg := range listenArgs {
wg.Add(1) wg.Add(1)
go func() { go func(arg Args) {
defer wg.Done() defer wg.Done()
if err := listenAndServe(arg); err != nil { listenAndServe(arg)
if glog.V(LFATAL) { }(args)
glog.Errorln(err)
}
}
}()
} }
wg.Wait() wg.Wait()
} }

View File

@ -14,11 +14,12 @@ const (
) )
type clientSelector struct { type clientSelector struct {
arg Args methods []uint8
arg Args
} }
func (selector *clientSelector) Methods() []uint8 { func (selector *clientSelector) Methods() []uint8 {
return nil return selector.methods
} }
func (selector *clientSelector) Select(methods ...uint8) (method uint8) { func (selector *clientSelector) Select(methods ...uint8) (method uint8) {
@ -187,7 +188,7 @@ func handleSocks5Request(req *gosocks5.Request, conn net.Conn, arg Args) {
if glog.V(LINFO) { if glog.V(LINFO) {
glog.Infoln("socks5 connect:", req.Addr.String()) glog.Infoln("socks5 connect:", req.Addr.String())
} }
tconn, err := connect(req.Addr.String()) tconn, err := connect(ConnSocks5, req.Addr.String())
if err != nil { if err != nil {
if glog.V(LWARNING) { if glog.V(LWARNING) {
glog.Warningln("socks5 connect:", err) glog.Warningln("socks5 connect:", err)

61
util.go
View File

@ -2,7 +2,6 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"github.com/golang/glog" "github.com/golang/glog"
"io" "io"
@ -11,10 +10,10 @@ import (
"strings" "strings"
) )
// socks://admin:123456@localhost:8080 // socks://admin:123456@localhost:8080/tls
type Args struct { type Args struct {
Addr string // host:port Addr string // host:port
Protocol string // protocol: hs/http/socks/socks5/ss, default is hs(http+socks5) Protocol string // protocol: http&socks5/http/socks/socks5/ss, default is http&socks5
Transport string // transport: tcp/ws/tls, default is tcp(raw tcp) Transport string // transport: tcp/ws/tls, default is tcp(raw tcp)
User *url.Userinfo User *url.Userinfo
EncMeth string // data encryption method EncMeth string // data encryption method
@ -41,7 +40,7 @@ func parseArgs(rawurl string) (args []Args) {
for _, s := range ss { for _, s := range ss {
if !strings.Contains(s, "://") { if !strings.Contains(s, "://") {
s = "hs://" + s s = "tcp://" + s
} }
u, err := url.Parse(s) u, err := url.Parse(s)
if err != nil { if err != nil {
@ -58,23 +57,23 @@ func parseArgs(rawurl string) (args []Args) {
schemes := strings.Split(u.Scheme, "+") schemes := strings.Split(u.Scheme, "+")
if len(schemes) == 1 { if len(schemes) == 1 {
switch schemes[0] { arg.Protocol = schemes[0]
case "http", "socks", "socks5", "ss": arg.Transport = schemes[0]
arg.Protocol = schemes[0]
case "ws", "tls", "tcp":
arg.Transport = schemes[0]
}
} }
if len(schemes) == 2 { if len(schemes) == 2 {
arg.Protocol = schemes[0] arg.Protocol = schemes[0]
arg.Transport = schemes[1] arg.Transport = schemes[1]
} }
arg.Cert, err = tls.LoadX509KeyPair("cert.pem", "key.pem") switch arg.Protocol {
if err != nil { case "http", "socks", "socks5", "ss":
if glog.V(LFATAL) { default:
glog.Errorln(err, ", tls will not be supported") arg.Protocol = ""
} }
switch arg.Transport {
case "ws", "tls", "tcp":
default:
arg.Transport = "tcp"
} }
mp := strings.Split(strings.Trim(u.Path, "/"), ":") mp := strings.Split(strings.Trim(u.Path, "/"), ":")
@ -85,8 +84,15 @@ func parseArgs(rawurl string) (args []Args) {
arg.EncMeth = mp[0] arg.EncMeth = mp[0]
arg.EncPass = mp[1] arg.EncPass = mp[1]
} }
if glog.V(LINFO) {
glog.Infoln(arg) if arg.Transport == "tls" || arg.EncMeth == "tls" {
arg.Cert, err = tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
if glog.V(LFATAL) {
glog.Errorln(err)
}
continue
}
} }
args = append(args, arg) args = append(args, arg)
} }
@ -94,27 +100,6 @@ func parseArgs(rawurl string) (args []Args) {
return return
} }
func connect(addr string) (net.Conn, error) {
if !strings.Contains(addr, ":") {
addr += ":80"
}
/*
if proxyURL == nil {
return dial(addr)
}
switch proxyURL.Scheme {
case "socks": // socks5 proxy
return connectSocks5Proxy(addr)
case "http": // http proxy
fallthrough
default:
return connectHTTPProxy(addr)
}
*/
return nil, errors.New("not implemented")
}
// based on io.Copy // based on io.Copy
func Copy(dst io.Writer, src io.Reader) (written int64, err error) { func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
buf := make([]byte, 32*1024) buf := make([]byte, 32*1024)