mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 09:25:41 +08:00
1. general: restructured codes;
2. rule: add dnsserver to ip rule map; 3. dns: add DNSAnswerHandler so we can define a custom handler when a domain resolved
This commit is contained in:
parent
dd5ba37582
commit
652d49182a
@ -30,7 +30,8 @@ General:
|
|||||||
- Rule proxy based on destionation: [Config Examples](examples)
|
- 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
197
conf.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nadoo/conflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flag = conflag.New()
|
||||||
|
|
||||||
|
var conf struct {
|
||||||
|
Verbose bool
|
||||||
|
Strategy string
|
||||||
|
CheckWebSite string
|
||||||
|
CheckDuration int
|
||||||
|
Listen []string
|
||||||
|
Forward []string
|
||||||
|
RuleFile []string
|
||||||
|
RulesDir string
|
||||||
|
|
||||||
|
DNS string
|
||||||
|
DNSServer []string
|
||||||
|
|
||||||
|
IPSet string
|
||||||
|
|
||||||
|
rules []*RuleConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func confInit() {
|
||||||
|
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||||
|
flag.StringVar(&conf.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||||
|
flag.StringVar(&conf.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||||
|
flag.IntVar(&conf.CheckDuration, "checkduration", 30, "proxy check duration(seconds)")
|
||||||
|
flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT")
|
||||||
|
flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||||
|
flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path")
|
||||||
|
flag.StringVar(&conf.RulesDir, "rules-dir", "rules.d", "rule file folder")
|
||||||
|
|
||||||
|
flag.StringVar(&conf.DNS, "dns", "", "dns listen address")
|
||||||
|
flag.StringSliceUniqVar(&conf.DNSServer, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server")
|
||||||
|
|
||||||
|
flag.StringVar(&conf.IPSet, "ipset", "glider", "ipset name")
|
||||||
|
|
||||||
|
flag.Usage = usage
|
||||||
|
err := flag.Parse()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.Listen) == 0 && conf.DNS == "" {
|
||||||
|
flag.Usage()
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rulefiles
|
||||||
|
for _, ruleFile := range conf.RuleFile {
|
||||||
|
rule, err := NewRuleConfFromFile(ruleFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.rules = append(conf.rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleFolderFiles, _ := listDir(conf.RulesDir, ".rule")
|
||||||
|
for _, ruleFile := range ruleFolderFiles {
|
||||||
|
rule, err := NewRuleConfFromFile(ruleFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.rules = append(conf.rules, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleConf , every ruleForwarder points to a rule file
|
||||||
|
type RuleConf struct {
|
||||||
|
Forward []string
|
||||||
|
Strategy string
|
||||||
|
CheckWebSite string
|
||||||
|
CheckDuration int
|
||||||
|
|
||||||
|
DNSServer []string
|
||||||
|
IPSet string
|
||||||
|
|
||||||
|
Domain []string
|
||||||
|
IP []string
|
||||||
|
CIDR []string
|
||||||
|
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRuleConfFromFile .
|
||||||
|
func NewRuleConfFromFile(ruleFile string) (*RuleConf, error) {
|
||||||
|
p := &RuleConf{name: ruleFile}
|
||||||
|
|
||||||
|
f := conflag.NewFromFile("rule", ruleFile)
|
||||||
|
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||||
|
f.StringVar(&p.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||||
|
f.StringVar(&p.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||||
|
f.IntVar(&p.CheckDuration, "checkduration", 30, "proxy check duration(seconds)")
|
||||||
|
|
||||||
|
f.StringSliceUniqVar(&p.DNSServer, "dnsserver", nil, "remote dns server")
|
||||||
|
f.StringVar(&p.IPSet, "ipset", "", "ipset name")
|
||||||
|
|
||||||
|
f.StringSliceUniqVar(&p.Domain, "domain", nil, "domain")
|
||||||
|
f.StringSliceUniqVar(&p.IP, "ip", nil, "ip")
|
||||||
|
f.StringSliceUniqVar(&p.CIDR, "cidr", nil, "cidr")
|
||||||
|
|
||||||
|
err := f.Parse()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
app := os.Args[0]
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "%s v%s usage:\n", app, VERSION)
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available Schemas:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ss: ss proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " http: http proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tcptun: a simple tcp tunnel\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp)\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available schemas for different modes:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir tcptun dnstun\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " forward: ss socks5 http\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available methods for ss:\n")
|
||||||
|
// fmt.Fprintf(os.Stderr, " "+ListCipher())
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available forward strategies:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ha: High Availability mode\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Config file format(see `"+app+".conf.example` as an example):\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " # COMMENT LINE\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Examples:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -listen dnstun://:53=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
}
|
49
dialer.go
Normal file
49
dialer.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Dialer means to establish a connection and relay it.
|
||||||
|
type Dialer interface {
|
||||||
|
// Addr()
|
||||||
|
Addr() string
|
||||||
|
|
||||||
|
// Dial connects to the given address via the proxy.
|
||||||
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialerFromURL parses url and get a Proxy
|
||||||
|
// TODO: table
|
||||||
|
func DialerFromURL(s string, cDialer Dialer) (Dialer, error) {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
logf("parse err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := u.Host
|
||||||
|
var user, pass string
|
||||||
|
if u.User != nil {
|
||||||
|
user = u.User.Username()
|
||||||
|
pass, _ = u.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
if cDialer == nil {
|
||||||
|
cDialer = Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "http":
|
||||||
|
return NewHTTP(addr, cDialer, nil)
|
||||||
|
case "socks5":
|
||||||
|
return NewSOCKS5("tcp", addr, user, pass, cDialer, nil)
|
||||||
|
case "ss":
|
||||||
|
p, err := NewSS(addr, user, pass, cDialer, nil)
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||||
|
}
|
@ -9,14 +9,7 @@ type direct struct {
|
|||||||
// Direct proxy
|
// 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)
|
||||||
|
34
dns.go
34
dns.go
@ -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{}
|
||||||
|
|
||||||
|
18
dnstun.go
18
dnstun.go
@ -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
24
forwarder.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
// Forwarder .
|
||||||
|
type Forwarder struct {
|
||||||
|
addr string
|
||||||
|
cDialer Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForwarder .
|
||||||
|
func NewForwarder(addr string, cDialer Dialer) *Forwarder {
|
||||||
|
if cDialer == nil {
|
||||||
|
cDialer = Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Forwarder{addr: addr, cDialer: cDialer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Forwarder) Addr() string { return p.addr }
|
||||||
|
|
||||||
|
func (p *Forwarder) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return p.cDialer.Dial(network, addr)
|
||||||
|
}
|
30
http.go
30
http.go
@ -16,22 +16,24 @@ import (
|
|||||||
"time"
|
"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
|
||||||
|
183
main.go
183
main.go
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
mixed.go
108
mixed.go
@ -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,48 +61,49 @@ func (p *MixedProxy) ListenAndServe() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go p.Serve(c)
|
||||||
defer c.Close()
|
}
|
||||||
|
}
|
||||||
if c, ok := c.(*net.TCPConn); ok {
|
|
||||||
c.SetKeepAlive(true)
|
func (p *MixedProxy) Serve(conn net.Conn) {
|
||||||
}
|
defer conn.Close()
|
||||||
|
|
||||||
c := newConn(c)
|
if c, ok := conn.(*net.TCPConn); ok {
|
||||||
|
c.SetKeepAlive(true)
|
||||||
if p.socks5 != nil {
|
}
|
||||||
head, err := c.Peek(1)
|
|
||||||
if err != nil {
|
c := newConn(conn)
|
||||||
logf("peek error: %s", err)
|
|
||||||
return
|
if p.socks5 != nil {
|
||||||
}
|
head, err := c.Peek(1)
|
||||||
|
if err != nil {
|
||||||
// check socks5, client send socksversion: 5 as the first byte
|
logf("peek error: %s", err)
|
||||||
if head[0] == socks5Version {
|
return
|
||||||
p.socks5.Serve(c)
|
}
|
||||||
return
|
|
||||||
}
|
// check socks5, client send socksversion: 5 as the first byte
|
||||||
}
|
if head[0] == socks5Version {
|
||||||
|
p.socks5.Serve(c)
|
||||||
if p.http != nil {
|
return
|
||||||
head, err := c.Peek(8)
|
}
|
||||||
if err != nil {
|
}
|
||||||
logf("peek error: %s", err)
|
|
||||||
return
|
if p.http != nil {
|
||||||
}
|
head, err := c.Peek(8)
|
||||||
|
if err != nil {
|
||||||
for _, method := range httpMethods {
|
logf("peek error: %s", err)
|
||||||
if bytes.HasPrefix(head, method) {
|
return
|
||||||
p.http.Serve(c)
|
}
|
||||||
return
|
|
||||||
}
|
for _, method := range httpMethods {
|
||||||
}
|
if bytes.HasPrefix(head, method) {
|
||||||
}
|
p.http.Serve(c)
|
||||||
|
return
|
||||||
if p.ss != nil {
|
}
|
||||||
p.ss.Serve(c)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
if p.ss != nil {
|
||||||
|
p.ss.Serve(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
159
proxy.go
159
proxy.go
@ -1,159 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Proxy means to establish a connection and relay it.
|
|
||||||
type Proxy interface {
|
|
||||||
// Get address
|
|
||||||
Addr() string
|
|
||||||
|
|
||||||
// ListenAndServe as proxy server, use only in server mode.
|
|
||||||
ListenAndServe()
|
|
||||||
|
|
||||||
// Serve as proxy server, use only in server mode.
|
|
||||||
Serve(c net.Conn)
|
|
||||||
|
|
||||||
// Get current proxy
|
|
||||||
CurrentProxy() Proxy
|
|
||||||
|
|
||||||
// Get a proxy based on the destAddr and strategy
|
|
||||||
GetProxy(dstAddr string) Proxy
|
|
||||||
|
|
||||||
// Switch to the next proxy
|
|
||||||
NextProxy() Proxy
|
|
||||||
|
|
||||||
// Get the status of proxy
|
|
||||||
Enabled() bool
|
|
||||||
|
|
||||||
// Set the proxy status
|
|
||||||
SetEnable(enable bool)
|
|
||||||
|
|
||||||
// Dial connects to the given address via the proxy.
|
|
||||||
Dial(network, addr string) (c net.Conn, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxy
|
|
||||||
type proxy struct {
|
|
||||||
addr string
|
|
||||||
forward Proxy
|
|
||||||
enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProxy .
|
|
||||||
func NewProxy(addr string, forward Proxy) *proxy {
|
|
||||||
if forward == nil {
|
|
||||||
forward = Direct
|
|
||||||
}
|
|
||||||
|
|
||||||
return &proxy{addr: addr, forward: forward, enabled: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *proxy) Addr() string { return p.addr }
|
|
||||||
func (p *proxy) ListenAndServe() { logf("base proxy ListenAndServe") }
|
|
||||||
func (p *proxy) Serve(c net.Conn) { logf("base proxy Serve") }
|
|
||||||
func (p *proxy) CurrentProxy() Proxy { return p.forward }
|
|
||||||
func (p *proxy) GetProxy(dstAddr string) Proxy { return p.forward }
|
|
||||||
func (p *proxy) NextProxy() Proxy { return p.forward }
|
|
||||||
func (p *proxy) Enabled() bool { return p.enabled }
|
|
||||||
func (p *proxy) SetEnable(enable bool) { p.enabled = enable }
|
|
||||||
|
|
||||||
func (p *proxy) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
return p.forward.Dial(network, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProxyFromURL parses url and get a Proxy
|
|
||||||
// TODO: table
|
|
||||||
func ProxyFromURL(s string, forwarder Proxy) (Proxy, error) {
|
|
||||||
if !strings.Contains(s, "://") {
|
|
||||||
s = "mixed://" + s
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
logf("parse err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := u.Host
|
|
||||||
var user, pass string
|
|
||||||
if u.User != nil {
|
|
||||||
user = u.User.Username()
|
|
||||||
pass, _ = u.User.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch u.Scheme {
|
|
||||||
case "mixed":
|
|
||||||
return NewMixedProxy("tcp", addr, user, pass, forwarder)
|
|
||||||
case "http":
|
|
||||||
return NewHTTPProxy(addr, forwarder)
|
|
||||||
case "socks5":
|
|
||||||
return NewSOCKS5Proxy("tcp", addr, user, pass, forwarder)
|
|
||||||
case "ss":
|
|
||||||
p, err := NewSSProxy(addr, user, pass, forwarder)
|
|
||||||
return p, err
|
|
||||||
case "redir":
|
|
||||||
return NewRedirProxy(addr, forwarder)
|
|
||||||
case "tcptun":
|
|
||||||
d := strings.Split(addr, "=")
|
|
||||||
return NewTCPTun(d[0], d[1], forwarder)
|
|
||||||
case "dnstun":
|
|
||||||
d := strings.Split(addr, "=")
|
|
||||||
return NewDNSTun(d[0], d[1], forwarder)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check proxy
|
|
||||||
func check(p Proxy, webhost string, duration int) {
|
|
||||||
retry := 1
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
|
|
||||||
if strings.IndexByte(webhost, ':') == -1 {
|
|
||||||
webhost = webhost + ":80"
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Duration(duration) * time.Second * time.Duration(retry>>1))
|
|
||||||
retry <<= 1
|
|
||||||
|
|
||||||
if retry > 16 {
|
|
||||||
retry = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
c, err := p.Dial("tcp", webhost)
|
|
||||||
if err != nil {
|
|
||||||
p.SetEnable(false)
|
|
||||||
logf("proxy-check %s -> %s, set to DISABLED. error in dial: %s", p.Addr(), webhost, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Write([]byte("GET / HTTP/1.0"))
|
|
||||||
c.Write([]byte("\r\n\r\n"))
|
|
||||||
|
|
||||||
_, err = io.ReadFull(c, buf)
|
|
||||||
if err != nil {
|
|
||||||
p.SetEnable(false)
|
|
||||||
logf("proxy-check %s -> %s, set to DISABLED. error in read: %s", p.Addr(), webhost, err)
|
|
||||||
} else if bytes.Equal([]byte("HTTP"), buf) {
|
|
||||||
p.SetEnable(true)
|
|
||||||
retry = 2
|
|
||||||
dialTime := time.Since(startTime)
|
|
||||||
logf("proxy-check %s -> %s, set to ENABLED. connect time: %s", p.Addr(), webhost, dialTime.String())
|
|
||||||
} else {
|
|
||||||
p.SetEnable(false)
|
|
||||||
logf("proxy-check %s -> %s, set to DISABLED. server response: %s", p.Addr(), webhost, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,13 +18,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RedirProxy struct {
|
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
|
||||||
|
@ -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.
|
||||||
|
150
rule.go
150
rule.go
@ -1,75 +1,119 @@
|
|||||||
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 err error
|
var forwarders []Dialer
|
||||||
for _, url := range strings.Split(chain, ",") {
|
for _, chain := range r.Forward {
|
||||||
forward, err = ProxyFromURL(url, 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 {
|
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 {
|
// find in domainMap
|
||||||
go check(forward, p.CheckWebSite, p.CheckDuration)
|
if d, ok := p.domainMap[domain]; ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Proxy = forwarder
|
return p.gDialer
|
||||||
|
}
|
||||||
return p, err
|
|
||||||
|
func (rd *RuleDialer) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
d := rd.NextDialer(addr)
|
||||||
|
return d.Dial(network, addr)
|
||||||
}
|
}
|
||||||
|
102
rules.go
102
rules.go
@ -1,102 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RulesForwarder .
|
|
||||||
type RulesForwarder struct {
|
|
||||||
globalForwarder Proxy
|
|
||||||
|
|
||||||
domainMap map[string]Proxy
|
|
||||||
ipMap map[string]Proxy
|
|
||||||
cidrMap map[string]Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRulesForwarder .
|
|
||||||
func NewRulesForwarder(ruleForwarders []*RuleForwarder, globalForwarder Proxy) Proxy {
|
|
||||||
|
|
||||||
if len(ruleForwarders) == 0 {
|
|
||||||
return globalForwarder
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &RulesForwarder{globalForwarder: globalForwarder}
|
|
||||||
|
|
||||||
for _, f := range ruleForwarders {
|
|
||||||
p.domainMap = make(map[string]Proxy)
|
|
||||||
for _, domain := range f.Domain {
|
|
||||||
p.domainMap[domain] = f.Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ipMap = make(map[string]Proxy)
|
|
||||||
for _, ip := range f.IP {
|
|
||||||
p.ipMap[ip] = f.Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cidrMap = make(map[string]Proxy)
|
|
||||||
for _, cidr := range f.CIDR {
|
|
||||||
p.cidrMap[cidr] = f.Proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RulesForwarder) Addr() string { return "rules forwarder" }
|
|
||||||
func (p *RulesForwarder) ListenAndServe() {}
|
|
||||||
func (p *RulesForwarder) Serve(c net.Conn) {}
|
|
||||||
func (p *RulesForwarder) CurrentProxy() Proxy { return p.globalForwarder.CurrentProxy() }
|
|
||||||
|
|
||||||
func (p *RulesForwarder) GetProxy(dstAddr string) Proxy {
|
|
||||||
|
|
||||||
// TODO: change to index finders
|
|
||||||
host, _, err := net.SplitHostPort(dstAddr)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: check here
|
|
||||||
logf("SplitHostPort ERROR: %s", err)
|
|
||||||
return p.globalForwarder.GetProxy(dstAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// find ip
|
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
|
||||||
// check ip
|
|
||||||
if proxy, ok := p.ipMap[ip.String()]; ok {
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// check cidr
|
|
||||||
// TODO: do not parse cidr every time
|
|
||||||
for cidrStr, proxy := range p.cidrMap {
|
|
||||||
if _, net, err := net.ParseCIDR(cidrStr); err == nil {
|
|
||||||
if net.Contains(ip) {
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
domainParts := strings.Split(host, ".")
|
|
||||||
length := len(domainParts)
|
|
||||||
for i := length - 2; i >= 0; i-- {
|
|
||||||
domain := strings.Join(domainParts[i:length], ".")
|
|
||||||
|
|
||||||
// find in domainMap
|
|
||||||
if proxy, ok := p.domainMap[domain]; ok {
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.globalForwarder.GetProxy(dstAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RulesForwarder) NextProxy() Proxy {
|
|
||||||
return p.globalForwarder.NextProxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RulesForwarder) Enabled() bool { return true }
|
|
||||||
func (p *RulesForwarder) SetEnable(enable bool) {}
|
|
||||||
|
|
||||||
func (p *RulesForwarder) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
return p.GetProxy(addr).Dial(network, addr)
|
|
||||||
}
|
|
63
server.go
Normal file
63
server.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server .
|
||||||
|
type Server interface {
|
||||||
|
// ListenAndServe as proxy server, use only in server mode.
|
||||||
|
ListenAndServe()
|
||||||
|
|
||||||
|
// Serve
|
||||||
|
// Serve(c net.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerFromURL parses url and get a Proxy
|
||||||
|
// TODO: table
|
||||||
|
func ServerFromURL(s string, sDialer Dialer) (Server, error) {
|
||||||
|
if !strings.Contains(s, "://") {
|
||||||
|
s = "mixed://" + s
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
logf("parse err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := u.Host
|
||||||
|
var user, pass string
|
||||||
|
if u.User != nil {
|
||||||
|
user = u.User.Username()
|
||||||
|
pass, _ = u.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
if sDialer == nil {
|
||||||
|
sDialer = Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "mixed":
|
||||||
|
return NewMixedProxy("tcp", addr, user, pass, sDialer)
|
||||||
|
case "http":
|
||||||
|
return NewHTTP(addr, nil, sDialer)
|
||||||
|
case "socks5":
|
||||||
|
return NewSOCKS5("tcp", addr, user, pass, nil, sDialer)
|
||||||
|
case "ss":
|
||||||
|
p, err := NewSS(addr, user, pass, nil, sDialer)
|
||||||
|
return p, err
|
||||||
|
case "redir":
|
||||||
|
return NewRedirProxy(addr, sDialer)
|
||||||
|
case "tcptun":
|
||||||
|
d := strings.Split(addr, "=")
|
||||||
|
return NewTCPTun(d[0], d[1], sDialer)
|
||||||
|
case "dnstun":
|
||||||
|
d := strings.Split(addr, "=")
|
||||||
|
return NewDNSTun(d[0], d[1], sDialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||||
|
}
|
35
socks5.go
35
socks5.go
@ -55,28 +55,31 @@ var socks5Errors = []string{
|
|||||||
"address type not supported",
|
"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),
|
||||||
user: user,
|
sDialer: sDialer,
|
||||||
password: pass,
|
user: user,
|
||||||
|
password: pass,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
27
ss.go
@ -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
|
||||||
|
159
strategy.go
159
strategy.go
@ -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)
|
|
||||||
}
|
}
|
||||||
|
13
tcptun.go
13
tcptun.go
@ -4,15 +4,18 @@ 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),
|
||||||
raddr: raddr,
|
sDialer: sDialer,
|
||||||
|
raddr: raddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user