From 668d5bc47038453e7798a6d840f6081058991a3f Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Thu, 13 Jul 2017 21:55:41 +0800 Subject: [PATCH] first commit --- .gitignore | 4 + README.md | 111 +++++++++++++ conn.go | 54 +++++++ direct.go | 24 +++ dnstun.go | 81 ++++++++++ http.go | 250 +++++++++++++++++++++++++++++ main.go | 165 +++++++++++++++++++ mixed.go | 105 +++++++++++++ proxy.go | 152 ++++++++++++++++++ redir.go | 114 ++++++++++++++ redir_win.go | 17 ++ socks5.go | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++ ss.go | 116 ++++++++++++++ strategy.go | 82 ++++++++++ tcptun.go | 66 ++++++++ 15 files changed, 1777 insertions(+) create mode 100644 conn.go create mode 100644 direct.go create mode 100644 dnstun.go create mode 100644 http.go create mode 100644 main.go create mode 100644 mixed.go create mode 100644 proxy.go create mode 100644 redir.go create mode 100644 redir_win.go create mode 100644 socks5.go create mode 100644 ss.go create mode 100644 strategy.go create mode 100644 tcptun.go diff --git a/.gitignore b/.gitignore index a1338d6..f32a420 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ + +# custom +glider +doc/ diff --git a/README.md b/README.md index d35e75f..d46cf24 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,113 @@ # glider glider is a forward proxy with several protocols support. + +## Install + + go get -u github.com/nadoo/glider + +## Build + + cd $GOPATH/src/github.com/nadoo/glider + go build + +## Usage +```bash +glider v0.1 usage: + -checksite string + proxy check address (default "www.apple.com:443") + -duration int + proxy check duration(seconds) (default 30) + -f value + forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT] + -l value + listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT + -s string + forward strategy, default: rr (default "rr") + -v verbose mode + +Available Schemas: + mixed: serve as a http/socks5 proxy on the same port. (default) + ss: ss proxy + socks5: socks5 proxy + http: http proxy + redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules) + tcptun: a simple tcp tunnel + dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp) + +Available schemas for different modes: + listen: mixed ss socks5 http redir tcptun dnstun + forward: ss socks5 http + +Available methods for ss: + AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 + +Available forward strategies: + rr: Round Robin mode + ha: High Availability mode + +Examples: + glider -l :8443 -v + -listen on :8443, serve as http/socks5 proxy on the same port. + + glider -l ss://AEAD_CHACHA20_POLY1305:pass@:8443 + -listen on 0.0.0.0:8443 as a shadowsocks server. + + glider -l socks5://:1080 -v + -listen on :1080 as a socks5 proxy server, in verbose mode. + + glider -l http://:8080 -f socks5://127.0.0.1:1080 -v + -listen on :8080 as a http proxy server, forward all requests via socks5 server. + + glider -l redir://:1081 -f ss://method:pass@1.1.1.1:443 + -listen on :1081 as a transparent redirect server, forward all requests via remote ss server. + + glider -l tcptun://:80=2.2.2.2:80 -f ss://method:pass@1.1.1.1:443 + -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server. + + glider -l socks5://:1080 -l http://:8080 -f ss://method:pass@1.1.1.1:443 + -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server. + + glider -l redir://:1081 -l dnstun://:53=8.8.8.8:53 -f ss://method:pass@server1:port1,ss://method:pass@server2:port2 + -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2. + + glider -l socks5://:1080 -f ss://method:pass@server1:port1 -f ss://method:pass@server2:port2 -s rr + -listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode. + + glider -l mixed://:8443 -f ss://method:pass@server1:port1 + -listen on :8443, serve as http/socks5 proxy, forward requests via server1. + + glider -l mixed://:8443?http=1.1.1.1:80 -f ss://method:pass@server1:port1 + -listen on :8443, serve as socks5 proxy, and forward all HTTP requests to 1.1.1.1:80. +``` + +## Service +```bash +cd /etc/systemd/system/ +vim glider.service +``` + +```bash +[Unit] +Description=glider +After=network.target + +[Service] +Type=simple +ExecStartPre=/bin/mkdir -p /run/glider +ExecStartPre=/bin/chown nobody:nobody /run/glider +ExecStart=/opt/glider/glider -l redir://:7070 -l dnstun://:5353=8.8.8.8:53 -f ss://AEAD_CHACHA20_POLY1305:pass@yourhost:8443 +ExecReload=/bin/kill -HUP $MAINPID +ExecStop=/bin/kill -INT $MAINPID +Restart=always +User=nobody +Group=nobody +UMask=0027 + +[Install] +WantedBy=multi-user.target +``` + +```bash +systemctl enable glider.service +systemctl start glider.service +``` diff --git a/conn.go b/conn.go new file mode 100644 index 0000000..944e6ef --- /dev/null +++ b/conn.go @@ -0,0 +1,54 @@ +package main + +import ( + "bufio" + "io" + "net" + "time" +) + +type conn struct { + r *bufio.Reader + net.Conn +} + +func newConn(c net.Conn) conn { + return conn{bufio.NewReader(c), c} +} + +func newConnSize(c net.Conn, n int) conn { + return conn{bufio.NewReaderSize(c, n), c} +} + +func (c conn) Peek(n int) ([]byte, error) { + return c.r.Peek(n) +} + +func (c conn) Read(p []byte) (int, error) { + return c.r.Read(p) +} + +func relay(left, right net.Conn) (int64, int64, error) { + type res struct { + N int64 + Err error + } + ch := make(chan res) + + go func() { + n, err := io.Copy(right, left) + right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right + left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left + ch <- res{n, err} + }() + + n, err := io.Copy(left, right) + right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right + left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left + rs := <-ch + + if err == nil { + err = rs.Err + } + return n, rs.N, err +} diff --git a/direct.go b/direct.go new file mode 100644 index 0000000..ceda39e --- /dev/null +++ b/direct.go @@ -0,0 +1,24 @@ +package main + +import "net" + +// direct proxy +type direct struct { + Proxy +} + +// Direct proxy +var Direct = &direct{Proxy: &proxy{addr: "127.0.0.1"}} + +// Direct proxy always enabled +func (d *direct) Enabled() bool { + return true +} + +func (d *direct) Dial(network, addr string) (net.Conn, error) { + c, err := net.Dial(network, addr) + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + return c, err +} diff --git a/dnstun.go b/dnstun.go new file mode 100644 index 0000000..f966878 --- /dev/null +++ b/dnstun.go @@ -0,0 +1,81 @@ +package main + +import ( + "encoding/binary" + "io/ioutil" + "net" +) + +type dnstun struct { + Proxy + addr string + raddr string +} + +// DNSTunProxy returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr +func DNSTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) { + s := &dnstun{ + Proxy: upProxy, + addr: addr, + raddr: raddr, + } + + return s, nil +} + +// ListenAndServe redirected requests as a server. +func (s *dnstun) ListenAndServe() { + l, err := net.ListenPacket("udp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + + logf("listening UDP on %s", s.addr) + + for { + defer l.Close() + + data := make([]byte, 512) + n, clientAddr, err := l.ReadFrom(data) + if err != nil { + logf("DNS local read error: %v", err) + continue + } + + data = data[:n] + go func() { + rc, err := s.GetProxy().Dial("tcp", s.raddr) + if err != nil { + logf("failed to connect to server %v: %v", s.raddr, err) + return + } + defer rc.Close() + + logf("proxy-dnstun %s[dns.udp] <-> %s[dns.tcp]", clientAddr.String(), s.raddr) + + // 2 bytes length after tcp header, before dns message + length := make([]byte, 2) + binary.BigEndian.PutUint16(length, uint16(len(data))) + rc.Write(length) + rc.Write(data) + + buf, err := ioutil.ReadAll(rc) + if err != nil { + logf("error in ioutil.ReadAll: %s\n", err) + return + } + + // length is not needed in udp dns response. (2 bytes) + // SEE RFC1035, section 4.2.2 TCP: The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. + if len(buf) > 2 { + msg := buf[2:] + _, err = l.WriteTo(msg, clientAddr) + if err != nil { + logf("error in local write: %s\n", err) + } + } + + }() + } +} diff --git a/http.go b/http.go new file mode 100644 index 0000000..d7134b0 --- /dev/null +++ b/http.go @@ -0,0 +1,250 @@ +// http proxy +// NOTE: never keep-alive so the implementation can be much easier. + +package main + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net" + "net/textproto" + "net/url" + "strings" + "time" +) + +// httpproxy +type httpproxy struct { + Proxy + addr string +} + +// HTTPProxy returns a http proxy. +func HTTPProxy(addr string, upProxy Proxy) (Proxy, error) { + s := &httpproxy{ + Proxy: upProxy, + addr: addr, + } + + return s, nil +} + +// ListenAndServe . +func (s *httpproxy) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + + logf("listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +// Serve . +func (s *httpproxy) 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 := parseFirstLine(reqTP) + if !ok { + return + } + + if method == "CONNECT" { + s.servHTTPS(method, requestURI, proto, c) + return + } + + reqHeader, err := reqTP.ReadMIMEHeader() + if err != nil { + logf("read header error:%s", err) + return + } + cleanHeaders(reqHeader) + // tell the remote server not to keep alive + reqHeader.Set("Connection", "close") + + url, err := url.ParseRequestURI(requestURI) + if err != nil { + logf("parse request url error: %s", err) + return + } + + var tgt = url.Host + if !strings.Contains(url.Host, ":") { + tgt += ":80" + } + + rc, err := s.GetProxy().Dial("tcp", tgt) + if err != nil { + fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto) + logf("failed to dial: %v", err) + return + } + defer rc.Close() + + // GET http://example.com/a/index.htm HTTP/1.1 --> + // GET /a/index.htm HTTP/1.1 + url.Scheme = "" + url.Host = "" + uri := url.String() + + var reqBuf bytes.Buffer + writeFirstLine(method, uri, proto, &reqBuf) + writeHeaders(reqHeader, &reqBuf) + + // send request to remote server + rc.Write(reqBuf.Bytes()) + + // copy the left request bytes to remote server. eg. length specificed or chunked body. + go func() { + io.Copy(rc, reqR) + rc.SetDeadline(time.Now()) + c.SetDeadline(time.Now()) + }() + + respR := bufio.NewReader(rc) + respTP := textproto.NewReader(respR) + proto, code, status, ok := parseFirstLine(respTP) + if !ok { + return + } + + respHeader, err := respTP.ReadMIMEHeader() + if err != nil { + logf("read header error:%s", err) + return + } + + respHeader.Set("Proxy-Connection", "close") + respHeader.Set("Connection", "close") + + var respBuf bytes.Buffer + writeFirstLine(proto, code, status, &respBuf) + writeHeaders(respHeader, &respBuf) + + logf("proxy-http %s <-> %s", c.RemoteAddr(), tgt) + c.Write(respBuf.Bytes()) + + io.Copy(c, respR) + +} + +func (s *httpproxy) servHTTPS(method, requestURI, proto string, c net.Conn) { + rc, err := s.GetProxy().Dial("tcp", requestURI) + if err != nil { + c.Write([]byte(proto)) + c.Write([]byte(" 502 ERROR\r\n\r\n")) + logf("failed to dial: %v", err) + return + } + c.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n")) + + logf("proxy-https %s <-> %s", c.RemoteAddr(), requestURI) + + _, _, err = relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + logf("relay error: %v", err) + } +} + +// Dial connects to the address addr on the network net via the proxy. +func (s *httpproxy) Dial(network, addr string) (net.Conn, error) { + c, err := s.GetProxy().Dial("tcp", s.addr) + if err != nil { + logf("dial to %s error: %s", s.addr, err) + return nil, err + } + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + c.Write([]byte("CONNECT " + addr + " HTTP/1.0\r\n")) + // c.Write([]byte("Proxy-Connection: Keep-Alive\r\n")) + + var b [1024]byte + n, err := c.Read(b[:]) + if bytes.Contains(b[:n], []byte("200")) { + return c, err + } + + return nil, err +} + +// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. +func parseFirstLine(tp *textproto.Reader) (r1, r2, r3 string, ok bool) { + line, err := tp.ReadLine() + // logf("first line: %s", line) + if err != nil { + logf("read request line error:%s", err) + return + } + + s1 := strings.Index(line, " ") + s2 := strings.Index(line[s1+1:], " ") + if s1 < 0 || s2 < 0 { + return + } + s2 += s1 + 1 + return line[:s1], line[s1+1 : s2], line[s2+1:], true +} + +func cleanHeaders(header textproto.MIMEHeader) { + header.Del("Proxy-Connection") + header.Del("Connection") + header.Del("Keep-Alive") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") + header.Del("TE") + header.Del("Trailers") + header.Del("Transfer-Encoding") + header.Del("Upgrade") +} + +func writeFirstLine(s1, s2, s3 string, buf *bytes.Buffer) { + buf.Write([]byte(s1)) + buf.Write([]byte(" ")) + buf.Write([]byte(s2)) + buf.Write([]byte(" ")) + buf.Write([]byte(s3)) + buf.Write([]byte("\r\n")) +} + +func writeHeaders(header textproto.MIMEHeader, buf *bytes.Buffer) { + for key, values := range header { + buf.Write([]byte(key)) + buf.Write([]byte(": ")) + for k, v := range values { + buf.Write([]byte(v)) + if k > 0 { + buf.Write([]byte(" ")) + } + } + buf.Write([]byte("\r\n")) + } + + //header ended + buf.Write([]byte("\r\n")) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d9071d8 --- /dev/null +++ b/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/signal" + "strings" + "syscall" +) + +const version = "0.1" + +var config struct { + Verbose bool + Strategy string + CheckSite string + CheckDuration int +} + +func logf(f string, v ...interface{}) { + if config.Verbose { + log.Printf(f, v...) + } +} + +func usage() { + app := os.Args[0] + fmt.Fprintf(os.Stderr, "%s v%s usage:\n", app, version) + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available Schemas:\n") + fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n") + fmt.Fprintf(os.Stderr, " ss: ss proxy\n") + fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n") + fmt.Fprintf(os.Stderr, " http: http proxy\n") + fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n") + fmt.Fprintf(os.Stderr, " tcptun: a simple tcp tunnel\n") + fmt.Fprintf(os.Stderr, " dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp)\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available schemas for different modes:\n") + fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir tcptun dnstun\n") + fmt.Fprintf(os.Stderr, " forward: ss socks5 http\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available methods for ss:\n") + fmt.Fprintf(os.Stderr, " "+ListCipher()) + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available forward strategies:\n") + fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n") + fmt.Fprintf(os.Stderr, " ha: High Availability mode\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Examples:\n") + fmt.Fprintf(os.Stderr, " "+app+" -l :8443 -v\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+" -l ss://AEAD_CHACHA20_POLY1305:pass@:8443\n") + fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a shadowsocks server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l socks5://:1080 -v\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l http://:8080 -f socks5://127.0.0.1:1080 -v\n") + fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l redir://:1081 -f ss://method:pass@1.1.1.1:443\n") + fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l tcptun://:80=2.2.2.2:80 -f ss://method:pass@1.1.1.1:443\n") + fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l socks5://:1080 -l http://:8080 -f ss://method:pass@1.1.1.1:443\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l redir://:1081 -l dnstun://:53=8.8.8.8:53 -f ss://method:pass@server1:port1,ss://method:pass@server2:port2\n") + fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l socks5://:1080 -f ss://method:pass@server1:port1 -f ss://method:pass@server2:port2 -s rr\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l mixed://:8443 -f ss://method:pass@server1:port1\n") + fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy, forward requests via server1.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -l mixed://:8443?http=1.1.1.1:80 -f ss://method:pass@server1:port1\n") + fmt.Fprintf(os.Stderr, " -listen on :8443, serve as socks5 proxy, and forward all HTTP requests to 1.1.1.1:80.\n") + fmt.Fprintf(os.Stderr, "\n") +} + +type arrFlags []string + +// implement flag.Value interface +func (i *arrFlags) String() string { + return "" +} + +// implement flag.Value interface +func (i *arrFlags) Set(value string) error { + *i = append(*i, value) + return nil +} + +func main() { + + var flags struct { + Listen arrFlags + Forward arrFlags + } + + flag.BoolVar(&config.Verbose, "v", false, "verbose mode") + flag.StringVar(&config.Strategy, "s", "rr", "forward strategy, default: rr") + flag.StringVar(&config.CheckSite, "checksite", "www.apple.com:443", "proxy check address") + flag.IntVar(&config.CheckDuration, "duration", 30, "proxy check duration(seconds)") + + flag.Var(&flags.Listen, "l", "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT") + flag.Var(&flags.Forward, "f", "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]") + + flag.Usage = usage + flag.Parse() + + if len(flags.Listen) == 0 { + flag.Usage() + fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n") + return + } + + var forwarders []Proxy + if len(flags.Forward) > 0 { + var err error + for _, chain := range flags.Forward { + var forward Proxy + for _, url := range strings.Split(chain, ",") { + forward, err = ProxyFromURL(url, forward) + if err != nil { + log.Fatal(err) + } + } + forwarders = append(forwarders, forward) + } + } + + for _, forward := range forwarders { + go check(forward, config.CheckSite, config.CheckDuration) + } + + if len(flags.Listen) > 0 { + for _, listen := range flags.Listen { + local, err := ProxyFromURL(listen, forwarders...) + if err != nil { + log.Fatal(err) + } + + go local.ListenAndServe() + } + } + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh +} diff --git a/mixed.go b/mixed.go new file mode 100644 index 0000000..a9a739c --- /dev/null +++ b/mixed.go @@ -0,0 +1,105 @@ +package main + +import ( + "bytes" + "net" +) + +// 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"), +} + +// mixedproxy +type mixedproxy struct { + Proxy + http Proxy + socks5 Proxy + ss Proxy +} + +// MixedProxy returns a http mixed proxy. +func MixedProxy(network, addr, user, pass string, upProxy Proxy) (Proxy, error) { + p := &mixedproxy{ + Proxy: upProxy, + } + + p.http, _ = HTTPProxy(addr, upProxy) + p.socks5, _ = SOCKS5Proxy(network, addr, user, pass, upProxy) + + if user != "" && pass != "" { + p.ss, _ = SSProxy(user, pass, upProxy) + } + + return p, nil +} + +// mixedproxy . +func (p *mixedproxy) ListenAndServe() { + l, err := net.Listen("tcp", p.Addr()) + if err != nil { + logf("failed to listen on %s: %v", p.Addr(), err) + return + } + + logf("listening TCP on %s", p.Addr()) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + + go func() { + defer c.Close() + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + c := newConn(c) + + if p.socks5 != nil { + head, err := c.Peek(1) + if err != nil { + logf("peek error: %s", err) + return + } + + // check socks5, client send socksversion: 5 as the first byte + if head[0] == socks5Version { + p.socks5.Serve(c) + return + } + } + + if p.http != nil { + head, err := c.Peek(8) + if err != nil { + logf("peek error: %s", err) + return + } + + for _, method := range httpMethods { + if bytes.HasPrefix(head, method) { + p.http.Serve(c) + return + } + } + } + + if p.ss != nil { + p.ss.Serve(c) + } + + }() + } +} diff --git a/proxy.go b/proxy.go new file mode 100644 index 0000000..5c45b48 --- /dev/null +++ b/proxy.go @@ -0,0 +1,152 @@ +package main + +import ( + "errors" + "net" + "net/url" + "strings" + "time" +) + +// A Proxy means to establish a connection and relay it. +type Proxy interface { + // ListenAndServe as proxy server, use only in server mode. + ListenAndServe() + + // Serve as proxy server, use only in server mode. + Serve(c net.Conn) + + // Get address + Addr() string + + // Get current proxy + CurrentProxy() Proxy + + // Get a proxy according to the strategy + GetProxy() Proxy + + // Switch to the next proxy + NextProxy() Proxy + + // Get the status of proxy + Enabled() bool + + // Set the proxy status + SetEnable(enable bool) + + // Dial connects to the given address via the proxy. + Dial(network, addr string) (c net.Conn, err error) +} + +// proxy +type proxy struct { + addr string + forward Proxy + enabled bool +} + +// newProxy . +func newProxy(addr string, forward Proxy) Proxy { + if forward == nil { + forward = Direct + } + + return &proxy{addr: addr, forward: forward, enabled: false} +} + +func (p *proxy) ListenAndServe() { logf("base proxy ListenAndServe") } +func (p *proxy) Serve(c net.Conn) { logf("base proxy Serve") } +func (p *proxy) CurrentProxy() Proxy { return p.forward } +func (p *proxy) GetProxy() Proxy { return p.forward } +func (p *proxy) NextProxy() Proxy { return p.forward } +func (p *proxy) Enabled() bool { return p.enabled } +func (p *proxy) SetEnable(enable bool) { p.enabled = enable } +func (p *proxy) Addr() string { return p.addr } + +func (p *proxy) Dial(network, addr string) (net.Conn, error) { + return p.forward.Dial(network, addr) +} + +// ProxyFromURL parses url and get a Proxy +// TODO: table +func ProxyFromURL(s string, forwarders ...Proxy) (Proxy, error) { + if !strings.Contains(s, "://") { + s = "mixed://" + s + } + + u, err := url.Parse(s) + if err != nil { + logf("parse err: %s", err) + return nil, err + } + + addr := u.Host + var user, pass string + if u.User != nil { + user = u.User.Username() + pass, _ = u.User.Password() + } + + var proxy Proxy + if forwarders == nil || len(forwarders) == 0 { + proxy = newProxy(addr, Direct) + } else if len(forwarders) == 1 { + proxy = newProxy(addr, forwarders[0]) + } else if len(forwarders) > 1 { + switch config.Strategy { + case "rr": + proxy = newRRProxy(addr, forwarders) + logf("forward to remote servers in round robin mode.") + case "ha": + proxy = newHAProxy(addr, forwarders) + logf("forward to remote servers in high availability mode.") + default: + logf("not supported forward mode '%s', just use the first forward server.", config.Strategy) + proxy = newProxy(addr, forwarders[0]) + } + } + + switch u.Scheme { + case "ss": + p, err := SSProxy(user, pass, proxy) + return p, err + case "socks5": + return SOCKS5Proxy("tcp", addr, user, pass, proxy) + case "redir": + return RedirProxy(addr, proxy) + case "tcptun": + d := strings.Split(addr, "=") + return TCPTunProxy(d[0], d[1], proxy) + case "dnstun": + d := strings.Split(addr, "=") + return DNSTunProxy(d[0], d[1], proxy) + case "http": + return HTTPProxy(addr, proxy) + case "mixed": + return MixedProxy("tcp", addr, user, pass, proxy) + } + + return nil, errors.New("unknown schema '" + u.Scheme + "'") +} + +// Check proxy +func check(p Proxy, target string, duration int) { + firstTime := true + for { + if !firstTime { + time.Sleep(time.Duration(duration) * time.Second) + } + firstTime = false + + c, err := p.Dial("tcp", target) + if err != nil { + logf("proxy %s check error: %s, set to disabled.", p.Addr(), err) + p.SetEnable(false) + continue + } + defer c.Close() + + p.SetEnable(true) + logf("proxy %s check ok.", p.Addr()) + } +} diff --git a/redir.go b/redir.go new file mode 100644 index 0000000..aad33ed --- /dev/null +++ b/redir.go @@ -0,0 +1,114 @@ +// +build !windows + +package main + +import ( + "errors" + "fmt" + "net" + "syscall" +) + +const SO_ORIGINAL_DST = 80 + +type redir struct { + Proxy + addr string +} + +// RedirProxy returns a redirect proxy. +func RedirProxy(addr string, upProxy Proxy) (Proxy, error) { + s := &redir{ + Proxy: upProxy, + addr: addr, + } + + return s, nil +} + +// ListenAndServe redirected requests as a server. +func (s *redir) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + + logf("listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + + go func() { + defer c.Close() + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + tgt, c, err := getOriginalDstAddr(c) + if err != nil { + logf("failed to get target address: %v", err) + return + } + + rc, err := s.GetProxy().Dial("tcp", tgt.String()) + if err != nil { + logf("failed to connect to target: %v", err) + return + } + defer rc.Close() + + logf("proxy-redir %s <-> %s", c.RemoteAddr(), tgt) + // go io.Copy(rc, c) + // io.Copy(c, rc) + + _, _, err = relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + logf("relay error: %v", err) + } + + }() + } +} + +func getOriginalDstAddr(conn net.Conn) (addr net.Addr, c *net.TCPConn, err error) { + defer conn.Close() + + fc, err := conn.(*net.TCPConn).File() + if err != nil { + return + } + defer fc.Close() + + mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST) + if err != nil { + return + } + + // only ipv4 support + ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7]) + port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3]) + addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port)) + if err != nil { + return + } + + cc, err := net.FileConn(fc) + if err != nil { + return + } + + c, ok := cc.(*net.TCPConn) + if !ok { + err = errors.New("not a TCP connection") + } + return +} diff --git a/redir_win.go b/redir_win.go new file mode 100644 index 0000000..c6227b1 --- /dev/null +++ b/redir_win.go @@ -0,0 +1,17 @@ +// +build windows + +package main + +import "log" + +type redir struct{ Proxy } + +// RedirProxy returns a redirect proxy. +func RedirProxy(addr string, upProxy Proxy) (Proxy, error) { + return &redir{Proxy: upProxy}, nil +} + +// ListenAndServe redirected requests as a server. +func (s *redir) ListenAndServe() { + log.Fatal("redir not supported on windows") +} diff --git a/socks5.go b/socks5.go new file mode 100644 index 0000000..6043b43 --- /dev/null +++ b/socks5.go @@ -0,0 +1,436 @@ +// socks5 client: +// https://github.com/golang/net/tree/master/proxy +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// socks5 server: +// https://github.com/shadowsocks/go-shadowsocks2/tree/master/socks + +package main + +import ( + "errors" + "io" + "net" + "strconv" +) + +const socks5Version = 5 + +const ( + socks5AuthNone = 0 + socks5AuthPassword = 2 +) + +// SOCKS request commands as defined in RFC 1928 section 4. +const ( + socks5Connect = 1 + socks5Bind = 2 + socks5UDPAssociate = 3 +) + +// SOCKS address types as defined in RFC 1928 section 5. +const ( + socks5IP4 = 1 + socks5Domain = 3 + socks5IP6 = 4 +) + +// MaxAddrLen is the maximum size of SOCKS address in bytes. +const MaxAddrLen = 1 + 1 + 255 + 2 + +// Addr represents a SOCKS address as defined in RFC 1928 section 5. +type Addr []byte + +var socks5Errors = []string{ + "", + "general failure", + "connection forbidden", + "network unreachable", + "host unreachable", + "connection refused", + "TTL expired", + "command not supported", + "address type not supported", +} + +type socks5 struct { + Proxy + network, addr string + user, password string +} + +// SOCKS5Proxy returns a Proxy that makes SOCKSv5 connections to the given address +// with an optional username and password. See RFC 1928. +func SOCKS5Proxy(network, addr, user, pass string, upProxy Proxy) (Proxy, error) { + s := &socks5{ + Proxy: upProxy, + addr: addr, + user: user, + password: pass, + } + + return s, nil +} + +// ListenAndServe connects to the address addr on the network net via the SOCKS5 proxy. +func (s *socks5) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + + logf("listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +func (s *socks5) Serve(c net.Conn) { + defer c.Close() + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + tgt, err := s.handshake(c) + if err != nil { + logf("failed to get target address: %v", err) + return + } + + rc, err := s.GetProxy().Dial("tcp", tgt.String()) + if err != nil { + logf("failed to connect to target: %v", err) + return + } + defer rc.Close() + + logf("proxy-socks5 %s <-> %s", c.RemoteAddr(), tgt) + + _, _, err = relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + logf("relay error: %v", err) + } +} + +// Dial connects to the address addr on the network net via the SOCKS5 proxy. +func (s *socks5) Dial(network, addr string) (net.Conn, error) { + switch network { + case "tcp", "tcp6", "tcp4": + default: + return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) + } + + c, err := s.GetProxy().Dial(s.network, s.addr) + if err != nil { + logf("dial to %s error: %s", s.addr, err) + return nil, err + } + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + if err := s.connect(c, addr); err != nil { + c.Close() + return nil, err + } + + return c, nil +} + +// connect takes an existing connection to a socks5 proxy server, +// and commands the server to extend that connection to target, +// which must be a canonical address with a host and port. +func (s *socks5) connect(conn net.Conn, target string) error { + host, portStr, err := net.SplitHostPort(target) + if err != nil { + return err + } + + port, err := strconv.Atoi(portStr) + if err != nil { + return errors.New("proxy: failed to parse port number: " + portStr) + } + if port < 1 || port > 0xffff { + return errors.New("proxy: port number out of range: " + portStr) + } + + // the size here is just an estimate + buf := make([]byte, 0, 6+len(host)) + + buf = append(buf, socks5Version) + if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { + buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword) + } else { + buf = append(buf, 1 /* num auth methods */, socks5AuthNone) + } + + if _, err := conn.Write(buf); err != nil { + return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + if buf[0] != 5 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + } + if buf[1] == 0xff { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + } + + if buf[1] == socks5AuthPassword { + buf = buf[:0] + buf = append(buf, 1 /* password protocol version */) + buf = append(buf, uint8(len(s.user))) + buf = append(buf, s.user...) + buf = append(buf, uint8(len(s.password))) + buf = append(buf, s.password...) + + if _, err := conn.Write(buf); err != nil { + return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if buf[1] != 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + } + } + + buf = buf[:0] + buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */) + + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, socks5IP4) + ip = ip4 + } else { + buf = append(buf, socks5IP6) + } + buf = append(buf, ip...) + } else { + if len(host) > 255 { + return errors.New("proxy: destination hostname too long: " + host) + } + buf = append(buf, socks5Domain) + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + buf = append(buf, byte(port>>8), byte(port)) + + if _, err := conn.Write(buf); err != nil { + return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:4]); err != nil { + return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + failure := "unknown error" + if int(buf[1]) < len(socks5Errors) { + failure = socks5Errors[buf[1]] + } + + if len(failure) > 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + } + + bytesToDiscard := 0 + switch buf[3] { + case socks5IP4: + bytesToDiscard = net.IPv4len + case socks5IP6: + bytesToDiscard = net.IPv6len + case socks5Domain: + _, err := io.ReadFull(conn, buf[:1]) + if err != nil { + return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + bytesToDiscard = int(buf[0]) + default: + return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) + } + + if cap(buf) < bytesToDiscard { + buf = make([]byte, bytesToDiscard) + } else { + buf = buf[:bytesToDiscard] + } + if _, err := io.ReadFull(conn, buf); err != nil { + return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + // Also need to discard the port number + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + return nil +} + +// Handshake fast-tracks SOCKS initialization to get target address to connect. +func (s *socks5) handshake(rw io.ReadWriter) (Addr, error) { + // Read RFC 1928 for request and reply structure and sizes. + buf := make([]byte, MaxAddrLen) + // read VER, NMETHODS, METHODS + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return nil, err + } + nmethods := buf[1] + if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil { + return nil, err + } + // write VER METHOD + if _, err := rw.Write([]byte{5, 0}); err != nil { + return nil, err + } + // read VER CMD RSV ATYP DST.ADDR DST.PORT + if _, err := io.ReadFull(rw, buf[:3]); err != nil { + return nil, err + } + if buf[1] != socks5Connect { + return nil, errors.New(socks5Errors[7]) + } + addr, err := readAddr(rw, buf) + if err != nil { + return nil, err + } + // write VER REP RSV ATYP BND.ADDR BND.PORT + _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + return addr, err +} + +// String serializes SOCKS address a to string form. +func (a Addr) String() string { + var host, port string + + switch a[0] { // address type + case socks5Domain: + host = string(a[2 : 2+int(a[1])]) + port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) + case socks5IP4: + host = net.IP(a[1 : 1+net.IPv4len]).String() + port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) + case socks5IP6: + host = net.IP(a[1 : 1+net.IPv6len]).String() + port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1])) + } + + return net.JoinHostPort(host, port) +} + +func readAddr(r io.Reader, b []byte) (Addr, error) { + if len(b) < MaxAddrLen { + return nil, io.ErrShortBuffer + } + _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type + if err != nil { + return nil, err + } + + switch b[0] { + case socks5Domain: + _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length + if err != nil { + return nil, err + } + _, err = io.ReadFull(r, b[2:2+int(b[1])+2]) + return b[:1+1+int(b[1])+2], err + case socks5IP4: + _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) + return b[:1+net.IPv4len+2], err + case socks5IP6: + _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) + return b[:1+net.IPv6len+2], err + } + + return nil, errors.New(socks5Errors[8]) +} + +// ReadAddr reads just enough bytes from r to get a valid Addr. +func ReadAddr(r io.Reader) (Addr, error) { + return readAddr(r, make([]byte, MaxAddrLen)) +} + +// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. +func SplitAddr(b []byte) Addr { + addrLen := 1 + if len(b) < addrLen { + return nil + } + + switch b[0] { + case socks5Domain: + if len(b) < 2 { + return nil + } + addrLen = 1 + 1 + int(b[1]) + 2 + case socks5IP4: + addrLen = 1 + net.IPv4len + 2 + case socks5IP6: + addrLen = 1 + net.IPv6len + 2 + default: + return nil + + } + + if len(b) < addrLen { + return nil + } + + return b[:addrLen] +} + +// ParseAddr parses the address in string s. Returns nil if failed. +func ParseAddr(s string) Addr { + var addr Addr + host, port, err := net.SplitHostPort(s) + if err != nil { + return nil + } + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + addr = make([]byte, 1+net.IPv4len+2) + addr[0] = socks5IP4 + copy(addr[1:], ip4) + } else { + addr = make([]byte, 1+net.IPv6len+2) + addr[0] = socks5IP6 + copy(addr[1:], ip) + } + } else { + if len(host) > 255 { + return nil + } + addr = make([]byte, 1+1+len(host)+2) + addr[0] = socks5Domain + addr[1] = byte(len(host)) + copy(addr[2:], host) + } + + portnum, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return nil + } + + addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum) + + return addr +} diff --git a/ss.go b/ss.go new file mode 100644 index 0000000..6d24c3b --- /dev/null +++ b/ss.go @@ -0,0 +1,116 @@ +package main + +import ( + "errors" + "log" + "net" + "strings" + + "github.com/shadowsocks/go-shadowsocks2/core" +) + +// Shadowsocks +type shadowsocks struct { + Proxy + core.StreamConnCipher +} + +// SSProxy returns a shadowsocks proxy. +func SSProxy(method, pass string, upProxy Proxy) (Proxy, error) { + ciph, err := core.PickCipher(method, nil, pass) + if err != nil { + log.Fatal(err) + } + + s := &shadowsocks{ + Proxy: upProxy, + StreamConnCipher: ciph, + } + + return s, nil +} + +// ListenAndServe shadowsocks requests as a server. +func (s *shadowsocks) ListenAndServe() { + l, err := net.Listen("tcp", s.Addr()) + if err != nil { + logf("failed to listen on %s: %v", s.Addr(), err) + return + } + + logf("listening TCP on %s", s.Addr()) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + go s.Serve(c) + } +} + +func (s *shadowsocks) Serve(c net.Conn) { + defer c.Close() + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + c = s.StreamConnCipher.StreamConn(c) + + tgt, err := ReadAddr(c) + if err != nil { + logf("failed to get target address: %v", err) + return + } + + rc, err := s.GetProxy().Dial("tcp", tgt.String()) + if err != nil { + logf("failed to connect to target: %v", err) + return + } + defer rc.Close() + + logf("proxy-ss %s <-> %s", c.RemoteAddr(), tgt) + + _, _, err = relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + logf("relay error: %v", err) + } + +} + +// Dial connects to the address addr on the network net via the proxy. +func (s *shadowsocks) Dial(network, addr string) (net.Conn, error) { + target := ParseAddr(addr) + if target == nil { + return nil, errors.New("Unable to parse address: " + addr) + } + + c, err := s.GetProxy().Dial("tcp", s.Addr()) + if err != nil { + logf("dial to %s error: %s", s.Addr(), err) + return nil, err + } + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + c = s.StreamConn(c) + if _, err = c.Write(target); err != nil { + c.Close() + return nil, err + } + + return c, err +} + +// ListCipher . +func ListCipher() string { + return strings.Join(core.ListCipher(), " ") +} diff --git a/strategy.go b/strategy.go new file mode 100644 index 0000000..5dc1706 --- /dev/null +++ b/strategy.go @@ -0,0 +1,82 @@ +package main + +import ( + "net" + "time" +) + +// strategyProxy +type strategyProxy struct { + addr string + forwarders []Proxy + idx int +} + +// newStrategyProxy . +func newStrategyProxy(addr string, forwarders []Proxy) Proxy { + if len(forwarders) == 0 { + return Direct + } else if len(forwarders) == 1 { + return newProxy(addr, forwarders[0]) + } + + return &strategyProxy{addr: addr, forwarders: forwarders} +} + +func (p *strategyProxy) ListenAndServe() {} +func (p *strategyProxy) Serve(c net.Conn) {} +func (p *strategyProxy) CurrentProxy() Proxy { return p.forwarders[p.idx] } +func (p *strategyProxy) GetProxy() Proxy { return p.NextProxy() } + +func (p *strategyProxy) NextProxy() Proxy { + n := len(p.forwarders) + if n == 1 { + return p.forwarders[0] + } + + p.idx = (p.idx + 1) % n + if !p.forwarders[p.idx].Enabled() { + return p.NextProxy() + } + + return p.forwarders[p.idx] +} + +func (p *strategyProxy) Enabled() bool { return true } +func (p *strategyProxy) SetEnable(enable bool) {} + +func (p *strategyProxy) Check(proxy Proxy, target string, duration time.Duration) {} + +func (p *strategyProxy) Addr() string { return p.addr } + +func (p *strategyProxy) Dial(network, addr string) (net.Conn, error) { + return p.NextProxy().Dial(network, addr) +} + +// round robin proxy +type rrproxy struct { + Proxy +} + +// newRRProxy . +func newRRProxy(addr string, forwarders []Proxy) Proxy { + return newStrategyProxy(addr, forwarders) +} + +// high availability proxy +type haproxy struct { + Proxy +} + +// newHAProxy . +func newHAProxy(addr string, forwarders []Proxy) Proxy { + return newStrategyProxy(addr, forwarders) +} + +func (p *haproxy) GetProxy() Proxy { + proxy := p.CurrentProxy() + if proxy.Enabled() == false { + return p.NextProxy() + } + return proxy +} diff --git a/tcptun.go b/tcptun.go new file mode 100644 index 0000000..7b6abd4 --- /dev/null +++ b/tcptun.go @@ -0,0 +1,66 @@ +package main + +import "net" + +type tcptun struct { + Proxy + addr string + raddr string +} + +// TCPTunProxy returns a redirect proxy. +func TCPTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) { + s := &tcptun{ + Proxy: upProxy, + addr: addr, + raddr: raddr, + } + + return s, nil +} + +// ListenAndServe redirected requests as a server. +func (s *tcptun) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + + logf("listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + + go func() { + defer c.Close() + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + rc, err := s.GetProxy().Dial("tcp", s.raddr) + if err != nil { + + logf("failed to connect to target: %v", err) + return + } + defer rc.Close() + + logf("proxy-tcptun %s <-> %s", c.RemoteAddr(), s.raddr) + + _, _, err = relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + logf("relay error: %v", err) + } + + }() + } +}