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)
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
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
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
View File

@ -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{}

View File

@ -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
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"
)
// 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
View File

@ -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
View File

@ -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
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 {
*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

View File

@ -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
View File

@ -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
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,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
View File

@ -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

View File

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

View File

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