mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 01:15:41 +08:00
1. general: restructured codes;
2. rule: add dnsserver to ip rule map; 3. dns: add DNSAnswerHandler so we can define a custom handler when a domain resolved
This commit is contained in:
parent
dd5ba37582
commit
652d49182a
@ -30,7 +30,8 @@ General:
|
||||
- Rule proxy based on destionation: [Config Examples](examples)
|
||||
|
||||
TODO:
|
||||
- Specify different remote dns server in rule file
|
||||
- Specify different remote dns server in rule file (DONE)
|
||||
- Improve DNS forwarder to resolve domain name and add ip to proxy rules (WIP)
|
||||
- IPSet management
|
||||
- Improve DNS forwarder to resolve domain name and add ip to ipset
|
||||
- TUN/TAP device support
|
||||
|
197
conf.go
Normal file
197
conf.go
Normal file
@ -0,0 +1,197 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/nadoo/conflag"
|
||||
)
|
||||
|
||||
var flag = conflag.New()
|
||||
|
||||
var conf struct {
|
||||
Verbose bool
|
||||
Strategy string
|
||||
CheckWebSite string
|
||||
CheckDuration int
|
||||
Listen []string
|
||||
Forward []string
|
||||
RuleFile []string
|
||||
RulesDir string
|
||||
|
||||
DNS string
|
||||
DNSServer []string
|
||||
|
||||
IPSet string
|
||||
|
||||
rules []*RuleConf
|
||||
}
|
||||
|
||||
func confInit() {
|
||||
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||
flag.StringVar(&conf.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
flag.StringVar(&conf.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
flag.IntVar(&conf.CheckDuration, "checkduration", 30, "proxy check duration(seconds)")
|
||||
flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT")
|
||||
flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||
flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path")
|
||||
flag.StringVar(&conf.RulesDir, "rules-dir", "rules.d", "rule file folder")
|
||||
|
||||
flag.StringVar(&conf.DNS, "dns", "", "dns listen address")
|
||||
flag.StringSliceUniqVar(&conf.DNSServer, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server")
|
||||
|
||||
flag.StringVar(&conf.IPSet, "ipset", "glider", "ipset name")
|
||||
|
||||
flag.Usage = usage
|
||||
err := flag.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
if len(conf.Listen) == 0 && conf.DNS == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// rulefiles
|
||||
for _, ruleFile := range conf.RuleFile {
|
||||
rule, err := NewRuleConfFromFile(ruleFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
conf.rules = append(conf.rules, rule)
|
||||
}
|
||||
|
||||
ruleFolderFiles, _ := listDir(conf.RulesDir, ".rule")
|
||||
for _, ruleFile := range ruleFolderFiles {
|
||||
rule, err := NewRuleConfFromFile(ruleFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
conf.rules = append(conf.rules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
// RuleConf , every ruleForwarder points to a rule file
|
||||
type RuleConf struct {
|
||||
Forward []string
|
||||
Strategy string
|
||||
CheckWebSite string
|
||||
CheckDuration int
|
||||
|
||||
DNSServer []string
|
||||
IPSet string
|
||||
|
||||
Domain []string
|
||||
IP []string
|
||||
CIDR []string
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
// NewRuleConfFromFile .
|
||||
func NewRuleConfFromFile(ruleFile string) (*RuleConf, error) {
|
||||
p := &RuleConf{name: ruleFile}
|
||||
|
||||
f := conflag.NewFromFile("rule", ruleFile)
|
||||
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||
f.StringVar(&p.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
f.StringVar(&p.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
f.IntVar(&p.CheckDuration, "checkduration", 30, "proxy check duration(seconds)")
|
||||
|
||||
f.StringSliceUniqVar(&p.DNSServer, "dnsserver", nil, "remote dns server")
|
||||
f.StringVar(&p.IPSet, "ipset", "", "ipset name")
|
||||
|
||||
f.StringSliceUniqVar(&p.Domain, "domain", nil, "domain")
|
||||
f.StringSliceUniqVar(&p.IP, "ip", nil, "ip")
|
||||
f.StringSliceUniqVar(&p.CIDR, "cidr", nil, "cidr")
|
||||
|
||||
err := f.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func usage() {
|
||||
app := os.Args[0]
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
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, " NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305\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, "Config file format(see `"+app+".conf.example` as an example):\n")
|
||||
fmt.Fprintf(os.Stderr, " # COMMENT LINE\n")
|
||||
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||
fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Examples:\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n")
|
||||
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n")
|
||||
fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\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+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\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+" -listen http://:8080 -forward socks5://127.0.0.1:1080\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+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\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+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\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+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\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+" -listen redir://:1081 -listen dnstun://:53=8.8.8.8:53 -forward 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+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy 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")
|
||||
}
|
49
dialer.go
Normal file
49
dialer.go
Normal file
@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// A Dialer means to establish a connection and relay it.
|
||||
type Dialer interface {
|
||||
// Addr()
|
||||
Addr() string
|
||||
|
||||
// Dial connects to the given address via the proxy.
|
||||
Dial(network, addr string) (c net.Conn, err error)
|
||||
}
|
||||
|
||||
// DialerFromURL parses url and get a Proxy
|
||||
// TODO: table
|
||||
func DialerFromURL(s string, cDialer Dialer) (Dialer, error) {
|
||||
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()
|
||||
}
|
||||
|
||||
if cDialer == nil {
|
||||
cDialer = Direct
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http":
|
||||
return NewHTTP(addr, cDialer, nil)
|
||||
case "socks5":
|
||||
return NewSOCKS5("tcp", addr, user, pass, cDialer, nil)
|
||||
case "ss":
|
||||
p, err := NewSS(addr, user, pass, cDialer, nil)
|
||||
return p, err
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||
}
|
@ -9,14 +9,7 @@ type direct struct {
|
||||
// Direct proxy
|
||||
var Direct = &direct{}
|
||||
|
||||
func (d *direct) Addr() string { return "127.0.0.1" }
|
||||
func (d *direct) ListenAndServe() { logf("direct proxy ListenAndServe") }
|
||||
func (d *direct) Serve(c net.Conn) { logf("direct proxy Serve") }
|
||||
func (d *direct) CurrentProxy() Proxy { return d }
|
||||
func (d *direct) GetProxy(dstAddr string) Proxy { return d }
|
||||
func (d *direct) NextProxy() Proxy { return d }
|
||||
func (d *direct) Enabled() bool { return true }
|
||||
func (d *direct) SetEnable(enable bool) {}
|
||||
func (d *direct) Addr() string { return "DIRECT" }
|
||||
|
||||
func (d *direct) Dial(network, addr string) (net.Conn, error) {
|
||||
c, err := net.Dial(network, addr)
|
||||
|
34
dns.go
34
dns.go
@ -38,7 +38,7 @@ type dnsQuery struct {
|
||||
}
|
||||
|
||||
type dnsAnswer struct {
|
||||
DomainName string
|
||||
// DomainName string
|
||||
QueryType uint16
|
||||
QueryClass uint16
|
||||
TTL uint32
|
||||
@ -48,18 +48,26 @@ type dnsAnswer struct {
|
||||
IP string
|
||||
}
|
||||
|
||||
// DNSAnswerHandler .
|
||||
type DNSAnswerHandler func(domain, ip string) error
|
||||
|
||||
// DNS .
|
||||
type DNS struct {
|
||||
*proxy
|
||||
*Forwarder // as proxy client
|
||||
sDialer Dialer // dialer for server
|
||||
|
||||
dnsServer string
|
||||
|
||||
dnsServerMap map[string]string
|
||||
dnsServerMap map[string]string
|
||||
answerHandlers []DNSAnswerHandler
|
||||
}
|
||||
|
||||
// NewDNS returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr
|
||||
func NewDNS(addr, raddr string, upProxy Proxy) (*DNS, error) {
|
||||
func NewDNS(addr, raddr string, sDialer Dialer) (*DNS, error) {
|
||||
s := &DNS{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
Forwarder: NewForwarder(addr, nil),
|
||||
sDialer: sDialer,
|
||||
|
||||
dnsServer: raddr,
|
||||
dnsServerMap: make(map[string]string),
|
||||
}
|
||||
@ -95,8 +103,9 @@ func (s *DNS) ListenAndServe() {
|
||||
domain := query.DomainName
|
||||
|
||||
dnsServer := s.GetServer(domain)
|
||||
// TODO: check here
|
||||
rc, err := s.GetProxy(domain+":53").Dial("tcp", dnsServer)
|
||||
|
||||
// TODO: check here; ADD dnsServer to rule ip lists
|
||||
rc, err := s.sDialer.Dial("tcp", dnsServer)
|
||||
if err != nil {
|
||||
logf("failed to connect to server %v: %v", dnsServer, err)
|
||||
return
|
||||
@ -129,6 +138,10 @@ func (s *DNS) ListenAndServe() {
|
||||
ip += answer.IP + ","
|
||||
}
|
||||
|
||||
for _, h := range s.answerHandlers {
|
||||
h(query.DomainName, answer.IP)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -139,7 +152,7 @@ func (s *DNS) ListenAndServe() {
|
||||
}
|
||||
}
|
||||
|
||||
logf("proxy-dns %s, %s <-> %s, ip: %s", domain, clientAddr.String(), dnsServer, ip)
|
||||
logf("proxy-dns %s <-> %s, %s: %s", clientAddr.String(), dnsServer, domain, ip)
|
||||
|
||||
}()
|
||||
}
|
||||
@ -166,6 +179,11 @@ func (s *DNS) GetServer(domain string) string {
|
||||
return s.dnsServer
|
||||
}
|
||||
|
||||
// AddAnswerHandler .
|
||||
func (s *DNS) AddAnswerHandler(h DNSAnswerHandler) {
|
||||
s.answerHandlers = append(s.answerHandlers, h)
|
||||
}
|
||||
|
||||
func parseQuery(p []byte) *dnsQuery {
|
||||
q := &dnsQuery{}
|
||||
|
||||
|
18
dnstun.go
18
dnstun.go
@ -4,22 +4,26 @@ package main
|
||||
|
||||
// DNSTun .
|
||||
type DNSTun struct {
|
||||
*proxy
|
||||
*Forwarder // as client
|
||||
sDialer Dialer // dialer for server
|
||||
|
||||
raddr string
|
||||
|
||||
udp Proxy
|
||||
tcp Proxy
|
||||
udp *DNS
|
||||
tcp *TCPTun
|
||||
}
|
||||
|
||||
// NewDNSTun returns a dns forwarder.
|
||||
func NewDNSTun(addr, raddr string, upProxy Proxy) (*DNSTun, error) {
|
||||
func NewDNSTun(addr, raddr string, sDialer Dialer) (*DNSTun, error) {
|
||||
s := &DNSTun{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
Forwarder: NewForwarder(addr, nil),
|
||||
sDialer: sDialer,
|
||||
|
||||
raddr: raddr,
|
||||
}
|
||||
|
||||
s.udp, _ = NewDNS(addr, raddr, upProxy)
|
||||
s.tcp, _ = NewTCPTun(addr, raddr, upProxy)
|
||||
s.udp, _ = NewDNS(addr, raddr, sDialer)
|
||||
s.tcp, _ = NewTCPTun(addr, raddr, sDialer)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
24
forwarder.go
Normal file
24
forwarder.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "net"
|
||||
|
||||
// Forwarder .
|
||||
type Forwarder struct {
|
||||
addr string
|
||||
cDialer Dialer
|
||||
}
|
||||
|
||||
// NewForwarder .
|
||||
func NewForwarder(addr string, cDialer Dialer) *Forwarder {
|
||||
if cDialer == nil {
|
||||
cDialer = Direct
|
||||
}
|
||||
|
||||
return &Forwarder{addr: addr, cDialer: cDialer}
|
||||
}
|
||||
|
||||
func (p *Forwarder) Addr() string { return p.addr }
|
||||
|
||||
func (p *Forwarder) Dial(network, addr string) (net.Conn, error) {
|
||||
return p.cDialer.Dial(network, addr)
|
||||
}
|
30
http.go
30
http.go
@ -16,22 +16,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPProxy .
|
||||
type HTTPProxy struct {
|
||||
*proxy
|
||||
// HTTP .
|
||||
type HTTP struct {
|
||||
*Forwarder // as client
|
||||
sDialer Dialer // dialer for server
|
||||
}
|
||||
|
||||
// NewHTTPProxy returns a http proxy.
|
||||
func NewHTTPProxy(addr string, upProxy Proxy) (*HTTPProxy, error) {
|
||||
s := &HTTPProxy{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
// NewHTTP returns a http proxy.
|
||||
func NewHTTP(addr string, cDialer Dialer, sDialer Dialer) (*HTTP, error) {
|
||||
s := &HTTP{
|
||||
Forwarder: NewForwarder(addr, cDialer),
|
||||
sDialer: sDialer,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe .
|
||||
func (s *HTTPProxy) ListenAndServe() {
|
||||
func (s *HTTP) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
@ -53,7 +55,7 @@ func (s *HTTPProxy) ListenAndServe() {
|
||||
}
|
||||
|
||||
// Serve .
|
||||
func (s *HTTPProxy) Serve(c net.Conn) {
|
||||
func (s *HTTP) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
@ -92,7 +94,7 @@ func (s *HTTPProxy) Serve(c net.Conn) {
|
||||
tgt += ":80"
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy(tgt).Dial("tcp", tgt)
|
||||
rc, err := s.sDialer.Dial("tcp", tgt)
|
||||
if err != nil {
|
||||
fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto)
|
||||
logf("failed to dial: %v", err)
|
||||
@ -147,8 +149,8 @@ func (s *HTTPProxy) Serve(c net.Conn) {
|
||||
|
||||
}
|
||||
|
||||
func (s *HTTPProxy) servHTTPS(method, requestURI, proto string, c net.Conn) {
|
||||
rc, err := s.GetProxy(requestURI).Dial("tcp", requestURI)
|
||||
func (s *HTTP) servHTTPS(method, requestURI, proto string, c net.Conn) {
|
||||
rc, err := s.sDialer.Dial("tcp", requestURI)
|
||||
if err != nil {
|
||||
c.Write([]byte(proto))
|
||||
c.Write([]byte(" 502 ERROR\r\n\r\n"))
|
||||
@ -170,8 +172,8 @@ func (s *HTTPProxy) servHTTPS(method, requestURI, proto string, c net.Conn) {
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *HTTPProxy) Dial(network, addr string) (net.Conn, error) {
|
||||
rc, err := s.GetProxy(s.addr).Dial("tcp", s.addr)
|
||||
func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
||||
rc, err := s.cDialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
|
183
main.go
183
main.go
@ -1,155 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/nadoo/conflag"
|
||||
)
|
||||
|
||||
// VERSION .
|
||||
const VERSION = "0.4.0"
|
||||
|
||||
var conf struct {
|
||||
Verbose bool
|
||||
Strategy string
|
||||
CheckWebSite string
|
||||
CheckDuration int
|
||||
Listen []string
|
||||
Forward []string
|
||||
RuleFile []string
|
||||
RulesDir string
|
||||
|
||||
DNS string
|
||||
DNSServer []string
|
||||
|
||||
IPSet string
|
||||
}
|
||||
|
||||
var flag = conflag.New()
|
||||
|
||||
func logf(f string, v ...interface{}) {
|
||||
if conf.Verbose {
|
||||
log.Printf(f, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
app := os.Args[0]
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
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, " NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305\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, "Config file format(see `"+app+".conf.example` as an example):\n")
|
||||
fmt.Fprintf(os.Stderr, " # COMMENT LINE\n")
|
||||
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||
fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Examples:\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n")
|
||||
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n")
|
||||
fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\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+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\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+" -listen http://:8080 -forward socks5://127.0.0.1:1080\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+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\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+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\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+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\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+" -listen redir://:1081 -listen dnstun://:53=8.8.8.8:53 -forward 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+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy 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")
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||
flag.StringVar(&conf.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
flag.StringVar(&conf.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
flag.IntVar(&conf.CheckDuration, "checkduration", 30, "proxy check duration(seconds)")
|
||||
flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT")
|
||||
flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||
flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path")
|
||||
flag.StringVar(&conf.RulesDir, "rules-dir", "rules.d", "rule file folder")
|
||||
|
||||
flag.StringVar(&conf.DNS, "dns", "", "dns listen address")
|
||||
flag.StringSliceUniqVar(&conf.DNSServer, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server")
|
||||
|
||||
flag.StringVar(&conf.IPSet, "ipset", "glider", "ipset name")
|
||||
|
||||
flag.Usage = usage
|
||||
err := flag.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(conf.Listen) == 0 && conf.DNS == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||
return
|
||||
}
|
||||
|
||||
func dialerFromConf() Dialer {
|
||||
// global forwarders in xx.conf
|
||||
var forwarders []Proxy
|
||||
var forwarders []Dialer
|
||||
for _, chain := range conf.Forward {
|
||||
var forward Proxy
|
||||
var forward Dialer
|
||||
var err error
|
||||
for _, url := range strings.Split(chain, ",") {
|
||||
forward, err = ProxyFromURL(url, forward)
|
||||
forward, err = DialerFromURL(url, forward)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -157,36 +32,18 @@ func main() {
|
||||
forwarders = append(forwarders, forward)
|
||||
}
|
||||
|
||||
// combine forwarders to a single strategy forwarder
|
||||
forwarder := NewStrategyForwarder(conf.Strategy, forwarders)
|
||||
forwarder := NewStrategyDialer(conf.Strategy, forwarders, conf.CheckWebSite, conf.CheckDuration)
|
||||
|
||||
// rule forwarders
|
||||
var ruleForwarders []*RuleForwarder
|
||||
for _, ruleFile := range conf.RuleFile {
|
||||
ruleForwarder, err := NewRuleForwarderFromFile(ruleFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return NewRuleDialer(conf.rules, forwarder)
|
||||
}
|
||||
|
||||
ruleForwarders = append(ruleForwarders, ruleForwarder)
|
||||
}
|
||||
func main() {
|
||||
|
||||
// rules folder
|
||||
ruleFolderFiles, _ := listDir(conf.RulesDir, ".rule")
|
||||
for _, ruleFile := range ruleFolderFiles {
|
||||
ruleForwarder, err := NewRuleForwarderFromFile(ruleFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ruleForwarders = append(ruleForwarders, ruleForwarder)
|
||||
}
|
||||
|
||||
// combine ruleforwarders and global strategy forwarder
|
||||
forwarder = NewRulesForwarder(ruleForwarders, forwarder)
|
||||
confInit()
|
||||
sDialer := dialerFromConf()
|
||||
|
||||
for _, listen := range conf.Listen {
|
||||
local, err := ProxyFromURL(listen, forwarder)
|
||||
local, err := ServerFromURL(listen, sDialer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -194,20 +51,14 @@ func main() {
|
||||
go local.ListenAndServe()
|
||||
}
|
||||
|
||||
if len(forwarders) > 1 {
|
||||
for _, forward := range forwarders {
|
||||
go check(forward, conf.CheckWebSite, conf.CheckDuration)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.DNS != "" {
|
||||
dns, err := NewDNS(conf.DNS, conf.DNSServer[0], forwarder)
|
||||
dns, err := NewDNS(conf.DNS, conf.DNSServer[0], sDialer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// rule
|
||||
for _, frwder := range ruleForwarders {
|
||||
for _, frwder := range conf.rules {
|
||||
for _, domain := range frwder.Domain {
|
||||
if len(frwder.DNSServer) > 0 {
|
||||
dns.SetServer(domain, frwder.DNSServer[0])
|
||||
@ -215,6 +66,14 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// test here
|
||||
dns.AddAnswerHandler(func(domain, ip string) error {
|
||||
if ip != "" {
|
||||
logf("domain: %s, ip: %s\n", domain, ip)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
go dns.ListenAndServe()
|
||||
}
|
||||
|
||||
|
108
mixed.go
108
mixed.go
@ -19,23 +19,26 @@ var httpMethods = [...][]byte{
|
||||
|
||||
// MixedProxy .
|
||||
type MixedProxy struct {
|
||||
*proxy
|
||||
http Proxy
|
||||
socks5 Proxy
|
||||
ss Proxy
|
||||
sDialer Dialer
|
||||
|
||||
addr string
|
||||
http *HTTP
|
||||
socks5 *SOCKS5
|
||||
ss *SS
|
||||
}
|
||||
|
||||
// NewMixedProxy returns a mixed proxy.
|
||||
func NewMixedProxy(network, addr, user, pass string, upProxy Proxy) (*MixedProxy, error) {
|
||||
func NewMixedProxy(network, addr, user, pass string, sDialer Dialer) (*MixedProxy, error) {
|
||||
p := &MixedProxy{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
sDialer: sDialer,
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
p.http, _ = NewHTTPProxy(addr, upProxy)
|
||||
p.socks5, _ = NewSOCKS5Proxy(network, addr, user, pass, upProxy)
|
||||
p.http, _ = NewHTTP(addr, nil, sDialer)
|
||||
p.socks5, _ = NewSOCKS5(network, addr, user, pass, nil, sDialer)
|
||||
|
||||
if user != "" && pass != "" {
|
||||
p.ss, _ = NewSSProxy(addr, user, pass, upProxy)
|
||||
p.ss, _ = NewSS(addr, user, pass, nil, sDialer)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
@ -58,48 +61,49 @@ func (p *MixedProxy) ListenAndServe() {
|
||||
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)
|
||||
}
|
||||
|
||||
}()
|
||||
go p.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MixedProxy) Serve(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
if c, ok := conn.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c := newConn(conn)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
159
proxy.go
159
proxy.go
@ -1,159 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Proxy means to establish a connection and relay it.
|
||||
type Proxy interface {
|
||||
// Get address
|
||||
Addr() string
|
||||
|
||||
// ListenAndServe as proxy server, use only in server mode.
|
||||
ListenAndServe()
|
||||
|
||||
// Serve as proxy server, use only in server mode.
|
||||
Serve(c net.Conn)
|
||||
|
||||
// Get current proxy
|
||||
CurrentProxy() Proxy
|
||||
|
||||
// Get a proxy based on the destAddr and strategy
|
||||
GetProxy(dstAddr string) 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: true}
|
||||
}
|
||||
|
||||
func (p *proxy) Addr() string { return p.addr }
|
||||
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(dstAddr string) 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) 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, forwarder 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()
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "mixed":
|
||||
return NewMixedProxy("tcp", addr, user, pass, forwarder)
|
||||
case "http":
|
||||
return NewHTTPProxy(addr, forwarder)
|
||||
case "socks5":
|
||||
return NewSOCKS5Proxy("tcp", addr, user, pass, forwarder)
|
||||
case "ss":
|
||||
p, err := NewSSProxy(addr, user, pass, forwarder)
|
||||
return p, err
|
||||
case "redir":
|
||||
return NewRedirProxy(addr, forwarder)
|
||||
case "tcptun":
|
||||
d := strings.Split(addr, "=")
|
||||
return NewTCPTun(d[0], d[1], forwarder)
|
||||
case "dnstun":
|
||||
d := strings.Split(addr, "=")
|
||||
return NewDNSTun(d[0], d[1], forwarder)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||
}
|
||||
|
||||
// Check proxy
|
||||
func check(p Proxy, webhost string, duration int) {
|
||||
retry := 1
|
||||
buf := make([]byte, 4)
|
||||
|
||||
if strings.IndexByte(webhost, ':') == -1 {
|
||||
webhost = webhost + ":80"
|
||||
}
|
||||
|
||||
for {
|
||||
time.Sleep(time.Duration(duration) * time.Second * time.Duration(retry>>1))
|
||||
retry <<= 1
|
||||
|
||||
if retry > 16 {
|
||||
retry = 16
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
c, err := p.Dial("tcp", webhost)
|
||||
if err != nil {
|
||||
p.SetEnable(false)
|
||||
logf("proxy-check %s -> %s, set to DISABLED. error in dial: %s", p.Addr(), webhost, err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.Write([]byte("GET / HTTP/1.0"))
|
||||
c.Write([]byte("\r\n\r\n"))
|
||||
|
||||
_, err = io.ReadFull(c, buf)
|
||||
if err != nil {
|
||||
p.SetEnable(false)
|
||||
logf("proxy-check %s -> %s, set to DISABLED. error in read: %s", p.Addr(), webhost, err)
|
||||
} else if bytes.Equal([]byte("HTTP"), buf) {
|
||||
p.SetEnable(true)
|
||||
retry = 2
|
||||
dialTime := time.Since(startTime)
|
||||
logf("proxy-check %s -> %s, set to ENABLED. connect time: %s", p.Addr(), webhost, dialTime.String())
|
||||
} else {
|
||||
p.SetEnable(false)
|
||||
logf("proxy-check %s -> %s, set to DISABLED. server response: %s", p.Addr(), webhost, buf)
|
||||
}
|
||||
|
||||
c.Close()
|
||||
}
|
||||
}
|
@ -18,13 +18,15 @@ const (
|
||||
)
|
||||
|
||||
type RedirProxy struct {
|
||||
*proxy
|
||||
*Forwarder // as client
|
||||
sDialer Dialer // dialer for server
|
||||
}
|
||||
|
||||
// NewRedirProxy returns a redirect proxy.
|
||||
func NewRedirProxy(addr string, upProxy Proxy) (*RedirProxy, error) {
|
||||
func NewRedirProxy(addr string, sDialer Dialer) (*RedirProxy, error) {
|
||||
s := &RedirProxy{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
Forwarder: NewForwarder(addr, nil),
|
||||
sDialer: sDialer,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@ -60,7 +62,7 @@ func (s *RedirProxy) ListenAndServe() {
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String())
|
||||
rc, err := s.sDialer.Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
|
@ -2,14 +2,17 @@
|
||||
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
// RedirProxy .
|
||||
type RedirProxy struct{ *proxy }
|
||||
type RedirProxy struct{}
|
||||
|
||||
// NewRedirProxy returns a redirect proxy.
|
||||
func NewRedirProxy(addr string, upProxy Proxy) (Proxy, error) {
|
||||
return &RedirProxy{proxy: NewProxy(addr, upProxy)}, nil
|
||||
func NewRedirProxy(addr string, sDialer Dialer) (*RedirProxy, error) {
|
||||
return nil, errors.New("redir not supported on this os")
|
||||
}
|
||||
|
||||
// ListenAndServe redirected requests as a server.
|
||||
|
150
rule.go
150
rule.go
@ -1,75 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/conflag"
|
||||
)
|
||||
|
||||
// RuleForwarder , every ruleForwarder points to a rule file
|
||||
type RuleForwarder struct {
|
||||
Forward []string
|
||||
Strategy string
|
||||
CheckWebSite string
|
||||
CheckDuration int
|
||||
// RuleDialer .
|
||||
type RuleDialer struct {
|
||||
gDialer Dialer
|
||||
|
||||
DNSServer []string
|
||||
IPSet string
|
||||
|
||||
Domain []string
|
||||
IP []string
|
||||
CIDR []string
|
||||
|
||||
name string
|
||||
Proxy
|
||||
domainMap map[string]Dialer
|
||||
ipMap map[string]Dialer
|
||||
cidrMap map[string]Dialer
|
||||
}
|
||||
|
||||
// NewRuleForwarderFromFile .
|
||||
func NewRuleForwarderFromFile(ruleFile string) (*RuleForwarder, error) {
|
||||
p := &RuleForwarder{name: ruleFile}
|
||||
// NewRuleDialer .
|
||||
func NewRuleDialer(rules []*RuleConf, gDialer Dialer) Dialer {
|
||||
|
||||
f := conflag.NewFromFile("rule", ruleFile)
|
||||
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||
f.StringVar(&p.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
f.StringVar(&p.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
f.IntVar(&p.CheckDuration, "checkduration", 30, "proxy check duration(seconds)")
|
||||
|
||||
f.StringSliceUniqVar(&p.DNSServer, "dnsserver", nil, "remote dns server")
|
||||
f.StringVar(&p.IPSet, "ipset", "", "ipset name")
|
||||
|
||||
f.StringSliceUniqVar(&p.Domain, "domain", nil, "domain")
|
||||
f.StringSliceUniqVar(&p.IP, "ip", nil, "ip")
|
||||
f.StringSliceUniqVar(&p.CIDR, "cidr", nil, "cidr")
|
||||
|
||||
err := f.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
return nil, err
|
||||
if len(rules) == 0 {
|
||||
return gDialer
|
||||
}
|
||||
|
||||
var forwarders []Proxy
|
||||
for _, chain := range p.Forward {
|
||||
var forward Proxy
|
||||
var err error
|
||||
for _, url := range strings.Split(chain, ",") {
|
||||
forward, err = ProxyFromURL(url, forward)
|
||||
rd := &RuleDialer{gDialer: gDialer}
|
||||
|
||||
for _, r := range rules {
|
||||
var forwarders []Dialer
|
||||
for _, chain := range r.Forward {
|
||||
var forward Dialer
|
||||
var err error
|
||||
for _, url := range strings.Split(chain, ",") {
|
||||
forward, err = DialerFromURL(url, forward)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
forwarders = append(forwarders, forward)
|
||||
}
|
||||
|
||||
sd := NewStrategyDialer(r.Strategy, forwarders, r.CheckWebSite, r.CheckDuration)
|
||||
|
||||
rd.domainMap = make(map[string]Dialer)
|
||||
for _, domain := range r.Domain {
|
||||
rd.domainMap[domain] = sd
|
||||
}
|
||||
|
||||
rd.ipMap = make(map[string]Dialer)
|
||||
for _, ip := range r.IP {
|
||||
rd.ipMap[ip] = sd
|
||||
}
|
||||
|
||||
// dnsserver should use rule forwarder too
|
||||
for _, dnss := range r.DNSServer {
|
||||
ip, _, err := net.SplitHostPort(dnss)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logf("SplitHostPort ERROR: %s", err)
|
||||
continue
|
||||
}
|
||||
rd.ipMap[ip] = sd
|
||||
}
|
||||
|
||||
rd.cidrMap = make(map[string]Dialer)
|
||||
for _, cidr := range r.CIDR {
|
||||
rd.cidrMap[cidr] = sd
|
||||
}
|
||||
}
|
||||
|
||||
return rd
|
||||
}
|
||||
|
||||
func (rd *RuleDialer) Addr() string { return "RULES" }
|
||||
|
||||
func (p *RuleDialer) NextDialer(dstAddr string) Dialer {
|
||||
|
||||
// TODO: change to index finders
|
||||
host, _, err := net.SplitHostPort(dstAddr)
|
||||
if err != nil {
|
||||
// TODO: check here
|
||||
logf("SplitHostPort ERROR: %s", err)
|
||||
return p.gDialer
|
||||
}
|
||||
|
||||
// find ip
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
// check ip
|
||||
if d, ok := p.ipMap[ip.String()]; ok {
|
||||
return d
|
||||
}
|
||||
|
||||
// check cidr
|
||||
// TODO: do not parse cidr every time
|
||||
for cidrStr, d := range p.cidrMap {
|
||||
if _, net, err := net.ParseCIDR(cidrStr); err == nil {
|
||||
if net.Contains(ip) {
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
forwarders = append(forwarders, forward)
|
||||
}
|
||||
|
||||
forwarder := NewStrategyForwarder(p.Strategy, forwarders)
|
||||
domainParts := strings.Split(host, ".")
|
||||
length := len(domainParts)
|
||||
for i := length - 2; i >= 0; i-- {
|
||||
domain := strings.Join(domainParts[i:length], ".")
|
||||
|
||||
for _, forward := range forwarders {
|
||||
go check(forward, p.CheckWebSite, p.CheckDuration)
|
||||
// find in domainMap
|
||||
if d, ok := p.domainMap[domain]; ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
p.Proxy = forwarder
|
||||
|
||||
return p, err
|
||||
return p.gDialer
|
||||
}
|
||||
|
||||
func (rd *RuleDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
d := rd.NextDialer(addr)
|
||||
return d.Dial(network, addr)
|
||||
}
|
||||
|
102
rules.go
102
rules.go
@ -1,102 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RulesForwarder .
|
||||
type RulesForwarder struct {
|
||||
globalForwarder Proxy
|
||||
|
||||
domainMap map[string]Proxy
|
||||
ipMap map[string]Proxy
|
||||
cidrMap map[string]Proxy
|
||||
}
|
||||
|
||||
// NewRulesForwarder .
|
||||
func NewRulesForwarder(ruleForwarders []*RuleForwarder, globalForwarder Proxy) Proxy {
|
||||
|
||||
if len(ruleForwarders) == 0 {
|
||||
return globalForwarder
|
||||
}
|
||||
|
||||
p := &RulesForwarder{globalForwarder: globalForwarder}
|
||||
|
||||
for _, f := range ruleForwarders {
|
||||
p.domainMap = make(map[string]Proxy)
|
||||
for _, domain := range f.Domain {
|
||||
p.domainMap[domain] = f.Proxy
|
||||
}
|
||||
|
||||
p.ipMap = make(map[string]Proxy)
|
||||
for _, ip := range f.IP {
|
||||
p.ipMap[ip] = f.Proxy
|
||||
}
|
||||
|
||||
p.cidrMap = make(map[string]Proxy)
|
||||
for _, cidr := range f.CIDR {
|
||||
p.cidrMap[cidr] = f.Proxy
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *RulesForwarder) Addr() string { return "rules forwarder" }
|
||||
func (p *RulesForwarder) ListenAndServe() {}
|
||||
func (p *RulesForwarder) Serve(c net.Conn) {}
|
||||
func (p *RulesForwarder) CurrentProxy() Proxy { return p.globalForwarder.CurrentProxy() }
|
||||
|
||||
func (p *RulesForwarder) GetProxy(dstAddr string) Proxy {
|
||||
|
||||
// TODO: change to index finders
|
||||
host, _, err := net.SplitHostPort(dstAddr)
|
||||
if err != nil {
|
||||
// TODO: check here
|
||||
logf("SplitHostPort ERROR: %s", err)
|
||||
return p.globalForwarder.GetProxy(dstAddr)
|
||||
}
|
||||
|
||||
// find ip
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
// check ip
|
||||
if proxy, ok := p.ipMap[ip.String()]; ok {
|
||||
return proxy
|
||||
}
|
||||
|
||||
// check cidr
|
||||
// TODO: do not parse cidr every time
|
||||
for cidrStr, proxy := range p.cidrMap {
|
||||
if _, net, err := net.ParseCIDR(cidrStr); err == nil {
|
||||
if net.Contains(ip) {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
domainParts := strings.Split(host, ".")
|
||||
length := len(domainParts)
|
||||
for i := length - 2; i >= 0; i-- {
|
||||
domain := strings.Join(domainParts[i:length], ".")
|
||||
|
||||
// find in domainMap
|
||||
if proxy, ok := p.domainMap[domain]; ok {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return p.globalForwarder.GetProxy(dstAddr)
|
||||
}
|
||||
|
||||
func (p *RulesForwarder) NextProxy() Proxy {
|
||||
return p.globalForwarder.NextProxy()
|
||||
}
|
||||
|
||||
func (p *RulesForwarder) Enabled() bool { return true }
|
||||
func (p *RulesForwarder) SetEnable(enable bool) {}
|
||||
|
||||
func (p *RulesForwarder) Dial(network, addr string) (net.Conn, error) {
|
||||
return p.GetProxy(addr).Dial(network, addr)
|
||||
}
|
63
server.go
Normal file
63
server.go
Normal file
@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server .
|
||||
type Server interface {
|
||||
// ListenAndServe as proxy server, use only in server mode.
|
||||
ListenAndServe()
|
||||
|
||||
// Serve
|
||||
// Serve(c net.Conn)
|
||||
}
|
||||
|
||||
// ServerFromURL parses url and get a Proxy
|
||||
// TODO: table
|
||||
func ServerFromURL(s string, sDialer Dialer) (Server, 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()
|
||||
}
|
||||
|
||||
if sDialer == nil {
|
||||
sDialer = Direct
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "mixed":
|
||||
return NewMixedProxy("tcp", addr, user, pass, sDialer)
|
||||
case "http":
|
||||
return NewHTTP(addr, nil, sDialer)
|
||||
case "socks5":
|
||||
return NewSOCKS5("tcp", addr, user, pass, nil, sDialer)
|
||||
case "ss":
|
||||
p, err := NewSS(addr, user, pass, nil, sDialer)
|
||||
return p, err
|
||||
case "redir":
|
||||
return NewRedirProxy(addr, sDialer)
|
||||
case "tcptun":
|
||||
d := strings.Split(addr, "=")
|
||||
return NewTCPTun(d[0], d[1], sDialer)
|
||||
case "dnstun":
|
||||
d := strings.Split(addr, "=")
|
||||
return NewDNSTun(d[0], d[1], sDialer)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||
}
|
35
socks5.go
35
socks5.go
@ -55,28 +55,31 @@ var socks5Errors = []string{
|
||||
"address type not supported",
|
||||
}
|
||||
|
||||
// SOCKS5Proxy .
|
||||
type SOCKS5Proxy struct {
|
||||
*proxy
|
||||
// SOCKS5 .
|
||||
type SOCKS5 struct {
|
||||
*Forwarder
|
||||
sDialer Dialer
|
||||
|
||||
network string
|
||||
user string
|
||||
password string
|
||||
}
|
||||
|
||||
// NewSOCKS5Proxy returns a Proxy that makes SOCKSv5 connections to the given address
|
||||
// NewSOCKS5 returns a Proxy that makes SOCKSv5 connections to the given address
|
||||
// with an optional username and password. See RFC 1928.
|
||||
func NewSOCKS5Proxy(network, addr, user, pass string, upProxy Proxy) (*SOCKS5Proxy, error) {
|
||||
s := &SOCKS5Proxy{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
user: user,
|
||||
password: pass,
|
||||
func NewSOCKS5(network, addr, user, pass string, cDialer Dialer, sDialer Dialer) (*SOCKS5, error) {
|
||||
s := &SOCKS5{
|
||||
Forwarder: NewForwarder(addr, cDialer),
|
||||
sDialer: sDialer,
|
||||
user: user,
|
||||
password: pass,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe connects to the address addr on the network net via the SOCKS5 proxy.
|
||||
func (s *SOCKS5Proxy) ListenAndServe() {
|
||||
func (s *SOCKS5) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
@ -97,7 +100,7 @@ func (s *SOCKS5Proxy) ListenAndServe() {
|
||||
}
|
||||
|
||||
// Serve .
|
||||
func (s *SOCKS5Proxy) Serve(c net.Conn) {
|
||||
func (s *SOCKS5) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
@ -110,7 +113,7 @@ func (s *SOCKS5Proxy) Serve(c net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String())
|
||||
rc, err := s.sDialer.Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
@ -129,14 +132,14 @@ func (s *SOCKS5Proxy) Serve(c net.Conn) {
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||||
func (s *SOCKS5Proxy) Dial(network, addr string) (net.Conn, error) {
|
||||
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(s.addr).Dial(s.network, s.addr)
|
||||
c, err := s.cDialer.Dial(s.network, s.addr)
|
||||
if err != nil {
|
||||
logf("dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
@ -157,7 +160,7 @@ func (s *SOCKS5Proxy) Dial(network, addr string) (net.Conn, error) {
|
||||
// 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 *SOCKS5Proxy) connect(conn net.Conn, target string) error {
|
||||
func (s *SOCKS5) connect(conn net.Conn, target string) error {
|
||||
host, portStr, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -288,7 +291,7 @@ func (s *SOCKS5Proxy) connect(conn net.Conn, target string) error {
|
||||
}
|
||||
|
||||
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
||||
func (s *SOCKS5Proxy) handshake(rw io.ReadWriter) (Addr, error) {
|
||||
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
|
||||
|
27
ss.go
27
ss.go
@ -9,21 +9,24 @@ import (
|
||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
// SSProxy .
|
||||
type SSProxy struct {
|
||||
*proxy
|
||||
// SS .
|
||||
type SS struct {
|
||||
*Forwarder
|
||||
sDialer Dialer
|
||||
|
||||
core.StreamConnCipher
|
||||
}
|
||||
|
||||
// NewSSProxy returns a shadowsocks proxy.
|
||||
func NewSSProxy(addr, method, pass string, upProxy Proxy) (*SSProxy, error) {
|
||||
// NewSS returns a shadowsocks proxy.
|
||||
func NewSS(addr, method, pass string, cDialer Dialer, sDialer Dialer) (*SS, error) {
|
||||
ciph, err := core.PickCipher(method, nil, pass)
|
||||
if err != nil {
|
||||
log.Fatalf("PickCipher for '%s', error: %s", method, err)
|
||||
}
|
||||
|
||||
s := &SSProxy{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
s := &SS{
|
||||
Forwarder: NewForwarder(addr, cDialer),
|
||||
sDialer: sDialer,
|
||||
StreamConnCipher: ciph,
|
||||
}
|
||||
|
||||
@ -31,7 +34,7 @@ func NewSSProxy(addr, method, pass string, upProxy Proxy) (*SSProxy, error) {
|
||||
}
|
||||
|
||||
// ListenAndServe shadowsocks requests as a server.
|
||||
func (s *SSProxy) ListenAndServe() {
|
||||
func (s *SS) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
@ -51,7 +54,7 @@ func (s *SSProxy) ListenAndServe() {
|
||||
}
|
||||
|
||||
// Serve .
|
||||
func (s *SSProxy) Serve(c net.Conn) {
|
||||
func (s *SS) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
@ -66,7 +69,7 @@ func (s *SSProxy) Serve(c net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String())
|
||||
rc, err := s.sDialer.Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
@ -86,14 +89,14 @@ func (s *SSProxy) Serve(c net.Conn) {
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *SSProxy) Dial(network, addr string) (net.Conn, error) {
|
||||
func (s *SS) 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(s.addr).Dial("tcp", s.addr)
|
||||
c, err := s.cDialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
|
159
strategy.go
159
strategy.go
@ -1,64 +1,80 @@
|
||||
package main
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewStrategyForwarder .
|
||||
func NewStrategyForwarder(strategy string, forwarders []Proxy) Proxy {
|
||||
var proxy Proxy
|
||||
if len(forwarders) == 0 {
|
||||
proxy = Direct
|
||||
} else if len(forwarders) == 1 {
|
||||
proxy = forwarders[0]
|
||||
} else if len(forwarders) > 1 {
|
||||
// NewStrategyDialer .
|
||||
func NewStrategyDialer(strategy string, dialers []Dialer, website string, duration int) Dialer {
|
||||
var dialer Dialer
|
||||
if len(dialers) == 0 {
|
||||
dialer = Direct
|
||||
} else if len(dialers) == 1 {
|
||||
dialer = dialers[0]
|
||||
} else if len(dialers) > 1 {
|
||||
switch strategy {
|
||||
case "rr":
|
||||
proxy = newRRProxy("", forwarders)
|
||||
dialer = newRRDialer(dialers, website, duration)
|
||||
logf("forward to remote servers in round robin mode.")
|
||||
case "ha":
|
||||
proxy = newHAProxy("", forwarders)
|
||||
dialer = newHADialer(dialers, website, duration)
|
||||
logf("forward to remote servers in high availability mode.")
|
||||
default:
|
||||
logf("not supported forward mode '%s', just use the first forward server.", conf.Strategy)
|
||||
proxy = forwarders[0]
|
||||
dialer = dialers[0]
|
||||
}
|
||||
}
|
||||
|
||||
return proxy
|
||||
return dialer
|
||||
}
|
||||
|
||||
// rrProxy
|
||||
type rrProxy struct {
|
||||
forwarders []Proxy
|
||||
idx int
|
||||
// rrDialer
|
||||
type rrDialer struct {
|
||||
dialers []Dialer
|
||||
idx int
|
||||
|
||||
status map[int]bool
|
||||
|
||||
// for checking
|
||||
website string
|
||||
duration int
|
||||
}
|
||||
|
||||
// newRRProxy .
|
||||
func newRRProxy(addr string, forwarders []Proxy) Proxy {
|
||||
if len(forwarders) == 0 {
|
||||
return Direct
|
||||
} else if len(forwarders) == 1 {
|
||||
return NewProxy(addr, forwarders[0])
|
||||
// newRRDialer .
|
||||
func newRRDialer(dialers []Dialer, website string, duration int) *rrDialer {
|
||||
rr := &rrDialer{dialers: dialers}
|
||||
|
||||
rr.status = make(map[int]bool)
|
||||
rr.website = website
|
||||
rr.duration = duration
|
||||
|
||||
for k := range dialers {
|
||||
rr.status[k] = true
|
||||
go rr.checkDialer(k)
|
||||
}
|
||||
|
||||
return &rrProxy{forwarders: forwarders}
|
||||
return rr
|
||||
}
|
||||
|
||||
func (p *rrProxy) Addr() string { return "strategy forwarder" }
|
||||
func (p *rrProxy) ListenAndServe() {}
|
||||
func (p *rrProxy) Serve(c net.Conn) {}
|
||||
func (p *rrProxy) CurrentProxy() Proxy { return p.forwarders[p.idx] }
|
||||
func (p *rrProxy) GetProxy(dstAddr string) Proxy { return p.NextProxy() }
|
||||
func (rr *rrDialer) Addr() string { return "STRATEGY" }
|
||||
func (rr *rrDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
return rr.NextDialer().Dial(network, addr)
|
||||
}
|
||||
|
||||
func (p *rrProxy) NextProxy() Proxy {
|
||||
n := len(p.forwarders)
|
||||
func (rr *rrDialer) NextDialer() Dialer {
|
||||
n := len(rr.dialers)
|
||||
if n == 1 {
|
||||
return p.forwarders[0]
|
||||
rr.idx = 0
|
||||
}
|
||||
|
||||
found := false
|
||||
for i := 0; i < n; i++ {
|
||||
p.idx = (p.idx + 1) % n
|
||||
if p.forwarders[p.idx].Enabled() {
|
||||
rr.idx = (rr.idx + 1) % n
|
||||
if rr.status[rr.idx] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@ -68,34 +84,73 @@ func (p *rrProxy) NextProxy() Proxy {
|
||||
logf("NO AVAILABLE PROXY FOUND! please check your network or proxy server settings.")
|
||||
}
|
||||
|
||||
return p.forwarders[p.idx]
|
||||
return rr.dialers[rr.idx]
|
||||
}
|
||||
|
||||
func (p *rrProxy) Enabled() bool { return true }
|
||||
func (p *rrProxy) SetEnable(enable bool) {}
|
||||
// Check dialer
|
||||
func (rr *rrDialer) checkDialer(idx int) {
|
||||
retry := 1
|
||||
buf := make([]byte, 4)
|
||||
|
||||
func (p *rrProxy) Dial(network, addr string) (net.Conn, error) {
|
||||
return p.GetProxy(addr).Dial(network, addr)
|
||||
if strings.IndexByte(rr.website, ':') == -1 {
|
||||
rr.website = rr.website + ":80"
|
||||
}
|
||||
|
||||
d := rr.dialers[idx]
|
||||
|
||||
for {
|
||||
time.Sleep(time.Duration(rr.duration) * time.Second * time.Duration(retry>>1))
|
||||
retry <<= 1
|
||||
|
||||
if retry > 16 {
|
||||
retry = 16
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
c, err := d.Dial("tcp", rr.website)
|
||||
if err != nil {
|
||||
rr.status[idx] = false
|
||||
logf("proxy-check %s -> %s, set to DISABLED. error in dial: %s", d.Addr(), rr.website, err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.Write([]byte("GET / HTTP/1.0"))
|
||||
c.Write([]byte("\r\n\r\n"))
|
||||
|
||||
_, err = io.ReadFull(c, buf)
|
||||
if err != nil {
|
||||
rr.status[idx] = false
|
||||
logf("proxy-check %s -> %s, set to DISABLED. error in read: %s", d.Addr(), rr.website, err)
|
||||
} else if bytes.Equal([]byte("HTTP"), buf) {
|
||||
rr.status[idx] = true
|
||||
retry = 2
|
||||
dialTime := time.Since(startTime)
|
||||
logf("proxy-check %s -> %s, set to ENABLED. connect time: %s", d.Addr(), rr.website, dialTime.String())
|
||||
} else {
|
||||
rr.status[idx] = false
|
||||
logf("proxy-check %s -> %s, set to DISABLED. server response: %s", d.Addr(), rr.website, buf)
|
||||
}
|
||||
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// high availability proxy
|
||||
type haProxy struct {
|
||||
Proxy
|
||||
type haDialer struct {
|
||||
*rrDialer
|
||||
}
|
||||
|
||||
// newHAProxy .
|
||||
func newHAProxy(addr string, forwarders []Proxy) Proxy {
|
||||
return &haProxy{Proxy: newRRProxy(addr, forwarders)}
|
||||
// newHADialer .
|
||||
func newHADialer(dialers []Dialer, webhost string, duration int) Dialer {
|
||||
return &haDialer{rrDialer: newRRDialer(dialers, webhost, duration)}
|
||||
}
|
||||
|
||||
func (p *haProxy) GetProxy(dstAddr string) Proxy {
|
||||
proxy := p.CurrentProxy()
|
||||
if proxy.Enabled() == false {
|
||||
return p.NextProxy()
|
||||
func (ha *haDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
d := ha.dialers[ha.idx]
|
||||
|
||||
if !ha.status[ha.idx] {
|
||||
d = ha.NextDialer()
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
|
||||
func (p *haProxy) Dial(network, addr string) (net.Conn, error) {
|
||||
return p.GetProxy(addr).Dial(network, addr)
|
||||
return d.Dial(network, addr)
|
||||
}
|
||||
|
13
tcptun.go
13
tcptun.go
@ -4,15 +4,18 @@ import "net"
|
||||
|
||||
// TCPTun .
|
||||
type TCPTun struct {
|
||||
*proxy
|
||||
*Forwarder
|
||||
sDialer Dialer
|
||||
|
||||
raddr string
|
||||
}
|
||||
|
||||
// NewTCPTun returns a redirect proxy.
|
||||
func NewTCPTun(addr, raddr string, upProxy Proxy) (*TCPTun, error) {
|
||||
func NewTCPTun(addr, raddr string, sDialer Dialer) (*TCPTun, error) {
|
||||
s := &TCPTun{
|
||||
proxy: NewProxy(addr, upProxy),
|
||||
raddr: raddr,
|
||||
Forwarder: NewForwarder(addr, nil),
|
||||
sDialer: sDialer,
|
||||
raddr: raddr,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@ -42,7 +45,7 @@ func (s *TCPTun) ListenAndServe() {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy(s.raddr).Dial("tcp", s.raddr)
|
||||
rc, err := s.sDialer.Dial("tcp", s.raddr)
|
||||
if err != nil {
|
||||
|
||||
logf("failed to connect to target: %v", err)
|
||||
|
Loading…
Reference in New Issue
Block a user