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:
nadoo 2017-08-23 16:35:39 +08:00
parent dd5ba37582
commit 652d49182a
20 changed files with 725 additions and 659 deletions

View File

@ -30,7 +30,8 @@ General:
- Rule proxy based on destionation: [Config Examples](examples) - Rule proxy based on destionation: [Config Examples](examples)
TODO: 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 - IPSet management
- Improve DNS forwarder to resolve domain name and add ip to ipset - Improve DNS forwarder to resolve domain name and add ip to ipset
- TUN/TAP device support - TUN/TAP device support

197
conf.go Normal file
View 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
View 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 + "'")
}

View File

@ -9,14 +9,7 @@ type direct struct {
// Direct proxy // Direct proxy
var Direct = &direct{} var Direct = &direct{}
func (d *direct) Addr() string { return "127.0.0.1" } func (d *direct) Addr() string { return "DIRECT" }
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) Dial(network, addr string) (net.Conn, error) { func (d *direct) Dial(network, addr string) (net.Conn, error) {
c, err := net.Dial(network, addr) c, err := net.Dial(network, addr)

32
dns.go
View File

@ -38,7 +38,7 @@ type dnsQuery struct {
} }
type dnsAnswer struct { type dnsAnswer struct {
DomainName string // DomainName string
QueryType uint16 QueryType uint16
QueryClass uint16 QueryClass uint16
TTL uint32 TTL uint32
@ -48,18 +48,26 @@ type dnsAnswer struct {
IP string IP string
} }
// DNSAnswerHandler .
type DNSAnswerHandler func(domain, ip string) error
// DNS . // DNS .
type DNS struct { type DNS struct {
*proxy *Forwarder // as proxy client
sDialer Dialer // dialer for server
dnsServer string 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 // 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{ s := &DNS{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, nil),
sDialer: sDialer,
dnsServer: raddr, dnsServer: raddr,
dnsServerMap: make(map[string]string), dnsServerMap: make(map[string]string),
} }
@ -95,8 +103,9 @@ func (s *DNS) ListenAndServe() {
domain := query.DomainName domain := query.DomainName
dnsServer := s.GetServer(domain) 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 { if err != nil {
logf("failed to connect to server %v: %v", dnsServer, err) logf("failed to connect to server %v: %v", dnsServer, err)
return return
@ -129,6 +138,10 @@ func (s *DNS) ListenAndServe() {
ip += answer.IP + "," 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 return s.dnsServer
} }
// AddAnswerHandler .
func (s *DNS) AddAnswerHandler(h DNSAnswerHandler) {
s.answerHandlers = append(s.answerHandlers, h)
}
func parseQuery(p []byte) *dnsQuery { func parseQuery(p []byte) *dnsQuery {
q := &dnsQuery{} q := &dnsQuery{}

View File

@ -4,22 +4,26 @@ package main
// DNSTun . // DNSTun .
type DNSTun struct { type DNSTun struct {
*proxy *Forwarder // as client
sDialer Dialer // dialer for server
raddr string raddr string
udp Proxy udp *DNS
tcp Proxy tcp *TCPTun
} }
// NewDNSTun returns a dns forwarder. // 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{ s := &DNSTun{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, nil),
sDialer: sDialer,
raddr: raddr, raddr: raddr,
} }
s.udp, _ = NewDNS(addr, raddr, upProxy) s.udp, _ = NewDNS(addr, raddr, sDialer)
s.tcp, _ = NewTCPTun(addr, raddr, upProxy) s.tcp, _ = NewTCPTun(addr, raddr, sDialer)
return s, nil return s, nil
} }

24
forwarder.go Normal file
View 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
View File

@ -16,22 +16,24 @@ import (
"time" "time"
) )
// HTTPProxy . // HTTP .
type HTTPProxy struct { type HTTP struct {
*proxy *Forwarder // as client
sDialer Dialer // dialer for server
} }
// NewHTTPProxy returns a http proxy. // NewHTTP returns a http proxy.
func NewHTTPProxy(addr string, upProxy Proxy) (*HTTPProxy, error) { func NewHTTP(addr string, cDialer Dialer, sDialer Dialer) (*HTTP, error) {
s := &HTTPProxy{ s := &HTTP{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, cDialer),
sDialer: sDialer,
} }
return s, nil return s, nil
} }
// ListenAndServe . // ListenAndServe .
func (s *HTTPProxy) ListenAndServe() { func (s *HTTP) ListenAndServe() {
l, err := net.Listen("tcp", s.addr) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
logf("failed to listen on %s: %v", s.addr, err) logf("failed to listen on %s: %v", s.addr, err)
@ -53,7 +55,7 @@ func (s *HTTPProxy) ListenAndServe() {
} }
// Serve . // Serve .
func (s *HTTPProxy) Serve(c net.Conn) { func (s *HTTP) Serve(c net.Conn) {
defer c.Close() defer c.Close()
if c, ok := c.(*net.TCPConn); ok { if c, ok := c.(*net.TCPConn); ok {
@ -92,7 +94,7 @@ func (s *HTTPProxy) Serve(c net.Conn) {
tgt += ":80" tgt += ":80"
} }
rc, err := s.GetProxy(tgt).Dial("tcp", tgt) rc, err := s.sDialer.Dial("tcp", tgt)
if err != nil { if err != nil {
fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto) fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto)
logf("failed to dial: %v", err) 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) { func (s *HTTP) servHTTPS(method, requestURI, proto string, c net.Conn) {
rc, err := s.GetProxy(requestURI).Dial("tcp", requestURI) rc, err := s.sDialer.Dial("tcp", requestURI)
if err != nil { if err != nil {
c.Write([]byte(proto)) c.Write([]byte(proto))
c.Write([]byte(" 502 ERROR\r\n\r\n")) 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. // Dial connects to the address addr on the network net via the proxy.
func (s *HTTPProxy) Dial(network, addr string) (net.Conn, error) { func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
rc, err := s.GetProxy(s.addr).Dial("tcp", s.addr) rc, err := s.cDialer.Dial("tcp", s.addr)
if err != nil { if err != nil {
logf("dial to %s error: %s", s.addr, err) logf("dial to %s error: %s", s.addr, err)
return nil, err return nil, err

181
main.go
View File

@ -1,155 +1,30 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
"syscall" "syscall"
"github.com/nadoo/conflag"
) )
// VERSION . // VERSION .
const VERSION = "0.4.0" 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{}) { func logf(f string, v ...interface{}) {
if conf.Verbose { if conf.Verbose {
log.Printf(f, v...) log.Printf(f, v...)
} }
} }
func usage() { func dialerFromConf() Dialer {
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
}
// global forwarders in xx.conf // global forwarders in xx.conf
var forwarders []Proxy var forwarders []Dialer
for _, chain := range conf.Forward { for _, chain := range conf.Forward {
var forward Proxy var forward Dialer
var err error var err error
for _, url := range strings.Split(chain, ",") { for _, url := range strings.Split(chain, ",") {
forward, err = ProxyFromURL(url, forward) forward, err = DialerFromURL(url, forward)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -157,36 +32,18 @@ func main() {
forwarders = append(forwarders, forward) forwarders = append(forwarders, forward)
} }
// combine forwarders to a single strategy forwarder forwarder := NewStrategyDialer(conf.Strategy, forwarders, conf.CheckWebSite, conf.CheckDuration)
forwarder := NewStrategyForwarder(conf.Strategy, forwarders)
// rule forwarders return NewRuleDialer(conf.rules, forwarder)
var ruleForwarders []*RuleForwarder
for _, ruleFile := range conf.RuleFile {
ruleForwarder, err := NewRuleForwarderFromFile(ruleFile)
if err != nil {
log.Fatal(err)
} }
ruleForwarders = append(ruleForwarders, ruleForwarder) func main() {
}
// rules folder confInit()
ruleFolderFiles, _ := listDir(conf.RulesDir, ".rule") sDialer := dialerFromConf()
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)
for _, listen := range conf.Listen { for _, listen := range conf.Listen {
local, err := ProxyFromURL(listen, forwarder) local, err := ServerFromURL(listen, sDialer)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -194,20 +51,14 @@ func main() {
go local.ListenAndServe() go local.ListenAndServe()
} }
if len(forwarders) > 1 {
for _, forward := range forwarders {
go check(forward, conf.CheckWebSite, conf.CheckDuration)
}
}
if conf.DNS != "" { if conf.DNS != "" {
dns, err := NewDNS(conf.DNS, conf.DNSServer[0], forwarder) dns, err := NewDNS(conf.DNS, conf.DNSServer[0], sDialer)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// rule // rule
for _, frwder := range ruleForwarders { for _, frwder := range conf.rules {
for _, domain := range frwder.Domain { for _, domain := range frwder.Domain {
if len(frwder.DNSServer) > 0 { if len(frwder.DNSServer) > 0 {
dns.SetServer(domain, 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() go dns.ListenAndServe()
} }

View File

@ -19,23 +19,26 @@ var httpMethods = [...][]byte{
// MixedProxy . // MixedProxy .
type MixedProxy struct { type MixedProxy struct {
*proxy sDialer Dialer
http Proxy
socks5 Proxy addr string
ss Proxy http *HTTP
socks5 *SOCKS5
ss *SS
} }
// NewMixedProxy returns a mixed proxy. // 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{ p := &MixedProxy{
proxy: NewProxy(addr, upProxy), sDialer: sDialer,
addr: addr,
} }
p.http, _ = NewHTTPProxy(addr, upProxy) p.http, _ = NewHTTP(addr, nil, sDialer)
p.socks5, _ = NewSOCKS5Proxy(network, addr, user, pass, upProxy) p.socks5, _ = NewSOCKS5(network, addr, user, pass, nil, sDialer)
if user != "" && pass != "" { if user != "" && pass != "" {
p.ss, _ = NewSSProxy(addr, user, pass, upProxy) p.ss, _ = NewSS(addr, user, pass, nil, sDialer)
} }
return p, nil return p, nil
@ -58,14 +61,18 @@ func (p *MixedProxy) ListenAndServe() {
continue continue
} }
go func() { go p.Serve(c)
defer c.Close() }
}
if c, ok := c.(*net.TCPConn); ok { func (p *MixedProxy) Serve(conn net.Conn) {
defer conn.Close()
if c, ok := conn.(*net.TCPConn); ok {
c.SetKeepAlive(true) c.SetKeepAlive(true)
} }
c := newConn(c) c := newConn(conn)
if p.socks5 != nil { if p.socks5 != nil {
head, err := c.Peek(1) head, err := c.Peek(1)
@ -99,7 +106,4 @@ func (p *MixedProxy) ListenAndServe() {
if p.ss != nil { if p.ss != nil {
p.ss.Serve(c) p.ss.Serve(c)
} }
}()
}
} }

159
proxy.go
View File

@ -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()
}
}

View File

@ -18,13 +18,15 @@ const (
) )
type RedirProxy struct { type RedirProxy struct {
*proxy *Forwarder // as client
sDialer Dialer // dialer for server
} }
// NewRedirProxy returns a redirect proxy. // NewRedirProxy returns a redirect proxy.
func NewRedirProxy(addr string, upProxy Proxy) (*RedirProxy, error) { func NewRedirProxy(addr string, sDialer Dialer) (*RedirProxy, error) {
s := &RedirProxy{ s := &RedirProxy{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, nil),
sDialer: sDialer,
} }
return s, nil return s, nil
@ -60,7 +62,7 @@ func (s *RedirProxy) ListenAndServe() {
return return
} }
rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String()) rc, err := s.sDialer.Dial("tcp", tgt.String())
if err != nil { if err != nil {
logf("failed to connect to target: %v", err) logf("failed to connect to target: %v", err)
return return

View File

@ -2,14 +2,17 @@
package main package main
import "log" import (
"errors"
"log"
)
// RedirProxy . // RedirProxy .
type RedirProxy struct{ *proxy } type RedirProxy struct{}
// NewRedirProxy returns a redirect proxy. // NewRedirProxy returns a redirect proxy.
func NewRedirProxy(addr string, upProxy Proxy) (Proxy, error) { func NewRedirProxy(addr string, sDialer Dialer) (*RedirProxy, error) {
return &RedirProxy{proxy: NewProxy(addr, upProxy)}, nil return nil, errors.New("redir not supported on this os")
} }
// ListenAndServe redirected requests as a server. // ListenAndServe redirected requests as a server.

142
rule.go
View File

@ -1,61 +1,36 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "net"
"strings" "strings"
"github.com/nadoo/conflag"
) )
// RuleForwarder , every ruleForwarder points to a rule file // RuleDialer .
type RuleForwarder struct { type RuleDialer struct {
Forward []string gDialer Dialer
Strategy string
CheckWebSite string
CheckDuration int
DNSServer []string domainMap map[string]Dialer
IPSet string ipMap map[string]Dialer
cidrMap map[string]Dialer
Domain []string
IP []string
CIDR []string
name string
Proxy
} }
// NewRuleForwarderFromFile . // NewRuleDialer .
func NewRuleForwarderFromFile(ruleFile string) (*RuleForwarder, error) { func NewRuleDialer(rules []*RuleConf, gDialer Dialer) Dialer {
p := &RuleForwarder{name: ruleFile}
f := conflag.NewFromFile("rule", ruleFile) if len(rules) == 0 {
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]") return gDialer
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
} }
var forwarders []Proxy rd := &RuleDialer{gDialer: gDialer}
for _, chain := range p.Forward {
var forward Proxy for _, r := range rules {
var forwarders []Dialer
for _, chain := range r.Forward {
var forward Dialer
var err error var err error
for _, url := range strings.Split(chain, ",") { for _, url := range strings.Split(chain, ",") {
forward, err = ProxyFromURL(url, forward) forward, err = DialerFromURL(url, forward)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -63,13 +38,82 @@ func NewRuleForwarderFromFile(ruleFile string) (*RuleForwarder, error) {
forwarders = append(forwarders, forward) forwarders = append(forwarders, forward)
} }
forwarder := NewStrategyForwarder(p.Strategy, forwarders) sd := NewStrategyDialer(r.Strategy, forwarders, r.CheckWebSite, r.CheckDuration)
for _, forward := range forwarders { rd.domainMap = make(map[string]Dialer)
go check(forward, p.CheckWebSite, p.CheckDuration) for _, domain := range r.Domain {
rd.domainMap[domain] = sd
} }
p.Proxy = forwarder rd.ipMap = make(map[string]Dialer)
for _, ip := range r.IP {
return p, err rd.ipMap[ip] = sd
}
// dnsserver should use rule forwarder too
for _, dnss := range r.DNSServer {
ip, _, err := net.SplitHostPort(dnss)
if err != nil {
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
}
}
}
}
domainParts := strings.Split(host, ".")
length := len(domainParts)
for i := length - 2; i >= 0; i-- {
domain := strings.Join(domainParts[i:length], ".")
// find in domainMap
if d, ok := p.domainMap[domain]; ok {
return d
}
}
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
View File

@ -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
View 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 + "'")
}

View File

@ -55,19 +55,22 @@ var socks5Errors = []string{
"address type not supported", "address type not supported",
} }
// SOCKS5Proxy . // SOCKS5 .
type SOCKS5Proxy struct { type SOCKS5 struct {
*proxy *Forwarder
sDialer Dialer
network string network string
user string user string
password 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. // with an optional username and password. See RFC 1928.
func NewSOCKS5Proxy(network, addr, user, pass string, upProxy Proxy) (*SOCKS5Proxy, error) { func NewSOCKS5(network, addr, user, pass string, cDialer Dialer, sDialer Dialer) (*SOCKS5, error) {
s := &SOCKS5Proxy{ s := &SOCKS5{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, cDialer),
sDialer: sDialer,
user: user, user: user,
password: pass, password: pass,
} }
@ -76,7 +79,7 @@ func NewSOCKS5Proxy(network, addr, user, pass string, upProxy Proxy) (*SOCKS5Pro
} }
// ListenAndServe connects to the address addr on the network net via the SOCKS5 proxy. // 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) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
logf("failed to listen on %s: %v", s.addr, err) logf("failed to listen on %s: %v", s.addr, err)
@ -97,7 +100,7 @@ func (s *SOCKS5Proxy) ListenAndServe() {
} }
// Serve . // Serve .
func (s *SOCKS5Proxy) Serve(c net.Conn) { func (s *SOCKS5) Serve(c net.Conn) {
defer c.Close() defer c.Close()
if c, ok := c.(*net.TCPConn); ok { if c, ok := c.(*net.TCPConn); ok {
@ -110,7 +113,7 @@ func (s *SOCKS5Proxy) Serve(c net.Conn) {
return return
} }
rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String()) rc, err := s.sDialer.Dial("tcp", tgt.String())
if err != nil { if err != nil {
logf("failed to connect to target: %v", err) logf("failed to connect to target: %v", err)
return 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. // 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 { switch network {
case "tcp", "tcp6", "tcp4": case "tcp", "tcp6", "tcp4":
default: default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) 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 { if err != nil {
logf("dial to %s error: %s", s.addr, err) logf("dial to %s error: %s", s.addr, err)
return nil, 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, // connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target, // and commands the server to extend that connection to target,
// which must be a canonical address with a host and port. // 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) host, portStr, err := net.SplitHostPort(target)
if err != nil { if err != nil {
return err 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. // 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. // Read RFC 1928 for request and reply structure and sizes.
buf := make([]byte, MaxAddrLen) buf := make([]byte, MaxAddrLen)
// read VER, NMETHODS, METHODS // read VER, NMETHODS, METHODS

27
ss.go
View File

@ -9,21 +9,24 @@ import (
"github.com/shadowsocks/go-shadowsocks2/core" "github.com/shadowsocks/go-shadowsocks2/core"
) )
// SSProxy . // SS .
type SSProxy struct { type SS struct {
*proxy *Forwarder
sDialer Dialer
core.StreamConnCipher core.StreamConnCipher
} }
// NewSSProxy returns a shadowsocks proxy. // NewSS returns a shadowsocks proxy.
func NewSSProxy(addr, method, pass string, upProxy Proxy) (*SSProxy, error) { func NewSS(addr, method, pass string, cDialer Dialer, sDialer Dialer) (*SS, error) {
ciph, err := core.PickCipher(method, nil, pass) ciph, err := core.PickCipher(method, nil, pass)
if err != nil { if err != nil {
log.Fatalf("PickCipher for '%s', error: %s", method, err) log.Fatalf("PickCipher for '%s', error: %s", method, err)
} }
s := &SSProxy{ s := &SS{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, cDialer),
sDialer: sDialer,
StreamConnCipher: ciph, StreamConnCipher: ciph,
} }
@ -31,7 +34,7 @@ func NewSSProxy(addr, method, pass string, upProxy Proxy) (*SSProxy, error) {
} }
// ListenAndServe shadowsocks requests as a server. // ListenAndServe shadowsocks requests as a server.
func (s *SSProxy) ListenAndServe() { func (s *SS) ListenAndServe() {
l, err := net.Listen("tcp", s.addr) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
logf("failed to listen on %s: %v", s.addr, err) logf("failed to listen on %s: %v", s.addr, err)
@ -51,7 +54,7 @@ func (s *SSProxy) ListenAndServe() {
} }
// Serve . // Serve .
func (s *SSProxy) Serve(c net.Conn) { func (s *SS) Serve(c net.Conn) {
defer c.Close() defer c.Close()
if c, ok := c.(*net.TCPConn); ok { if c, ok := c.(*net.TCPConn); ok {
@ -66,7 +69,7 @@ func (s *SSProxy) Serve(c net.Conn) {
return return
} }
rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String()) rc, err := s.sDialer.Dial("tcp", tgt.String())
if err != nil { if err != nil {
logf("failed to connect to target: %v", err) logf("failed to connect to target: %v", err)
return 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. // 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) target := ParseAddr(addr)
if target == nil { if target == nil {
return nil, errors.New("Unable to parse address: " + addr) 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 { if err != nil {
logf("dial to %s error: %s", s.addr, err) logf("dial to %s error: %s", s.addr, err)
return nil, err return nil, err

View File

@ -1,64 +1,80 @@
package main package main
import "net" import (
"bytes"
"io"
"net"
"strings"
"time"
)
// NewStrategyForwarder . // NewStrategyDialer .
func NewStrategyForwarder(strategy string, forwarders []Proxy) Proxy { func NewStrategyDialer(strategy string, dialers []Dialer, website string, duration int) Dialer {
var proxy Proxy var dialer Dialer
if len(forwarders) == 0 { if len(dialers) == 0 {
proxy = Direct dialer = Direct
} else if len(forwarders) == 1 { } else if len(dialers) == 1 {
proxy = forwarders[0] dialer = dialers[0]
} else if len(forwarders) > 1 { } else if len(dialers) > 1 {
switch strategy { switch strategy {
case "rr": case "rr":
proxy = newRRProxy("", forwarders) dialer = newRRDialer(dialers, website, duration)
logf("forward to remote servers in round robin mode.") logf("forward to remote servers in round robin mode.")
case "ha": case "ha":
proxy = newHAProxy("", forwarders) dialer = newHADialer(dialers, website, duration)
logf("forward to remote servers in high availability mode.") logf("forward to remote servers in high availability mode.")
default: default:
logf("not supported forward mode '%s', just use the first forward server.", conf.Strategy) 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 // rrDialer
type rrProxy struct { type rrDialer struct {
forwarders []Proxy dialers []Dialer
idx int idx int
status map[int]bool
// for checking
website string
duration int
} }
// newRRProxy . // newRRDialer .
func newRRProxy(addr string, forwarders []Proxy) Proxy { func newRRDialer(dialers []Dialer, website string, duration int) *rrDialer {
if len(forwarders) == 0 { rr := &rrDialer{dialers: dialers}
return Direct
} else if len(forwarders) == 1 { rr.status = make(map[int]bool)
return NewProxy(addr, forwarders[0]) 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 (rr *rrDialer) Addr() string { return "STRATEGY" }
func (p *rrProxy) ListenAndServe() {} func (rr *rrDialer) Dial(network, addr string) (net.Conn, error) {
func (p *rrProxy) Serve(c net.Conn) {} return rr.NextDialer().Dial(network, addr)
func (p *rrProxy) CurrentProxy() Proxy { return p.forwarders[p.idx] } }
func (p *rrProxy) GetProxy(dstAddr string) Proxy { return p.NextProxy() }
func (p *rrProxy) NextProxy() Proxy { func (rr *rrDialer) NextDialer() Dialer {
n := len(p.forwarders) n := len(rr.dialers)
if n == 1 { if n == 1 {
return p.forwarders[0] rr.idx = 0
} }
found := false found := false
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
p.idx = (p.idx + 1) % n rr.idx = (rr.idx + 1) % n
if p.forwarders[p.idx].Enabled() { if rr.status[rr.idx] {
found = true found = true
break break
} }
@ -68,34 +84,73 @@ func (p *rrProxy) NextProxy() Proxy {
logf("NO AVAILABLE PROXY FOUND! please check your network or proxy server settings.") 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 } // Check dialer
func (p *rrProxy) SetEnable(enable bool) {} func (rr *rrDialer) checkDialer(idx int) {
retry := 1
buf := make([]byte, 4)
func (p *rrProxy) Dial(network, addr string) (net.Conn, error) { if strings.IndexByte(rr.website, ':') == -1 {
return p.GetProxy(addr).Dial(network, addr) 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 // high availability proxy
type haProxy struct { type haDialer struct {
Proxy *rrDialer
} }
// newHAProxy . // newHADialer .
func newHAProxy(addr string, forwarders []Proxy) Proxy { func newHADialer(dialers []Dialer, webhost string, duration int) Dialer {
return &haProxy{Proxy: newRRProxy(addr, forwarders)} return &haDialer{rrDialer: newRRDialer(dialers, webhost, duration)}
} }
func (p *haProxy) GetProxy(dstAddr string) Proxy { func (ha *haDialer) Dial(network, addr string) (net.Conn, error) {
proxy := p.CurrentProxy() d := ha.dialers[ha.idx]
if proxy.Enabled() == false {
return p.NextProxy() if !ha.status[ha.idx] {
} d = ha.NextDialer()
return proxy
} }
func (p *haProxy) Dial(network, addr string) (net.Conn, error) { return d.Dial(network, addr)
return p.GetProxy(addr).Dial(network, addr)
} }

View File

@ -4,14 +4,17 @@ import "net"
// TCPTun . // TCPTun .
type TCPTun struct { type TCPTun struct {
*proxy *Forwarder
sDialer Dialer
raddr string raddr string
} }
// NewTCPTun returns a redirect proxy. // 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{ s := &TCPTun{
proxy: NewProxy(addr, upProxy), Forwarder: NewForwarder(addr, nil),
sDialer: sDialer,
raddr: raddr, raddr: raddr,
} }
@ -42,7 +45,7 @@ func (s *TCPTun) ListenAndServe() {
c.SetKeepAlive(true) c.SetKeepAlive(true)
} }
rc, err := s.GetProxy(s.raddr).Dial("tcp", s.raddr) rc, err := s.sDialer.Dial("tcp", s.raddr)
if err != nil { if err != nil {
logf("failed to connect to target: %v", err) logf("failed to connect to target: %v", err)