Skip to content

Commit

Permalink
Refactor proxy selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
cyfdecyf committed Jul 9, 2014
1 parent 21d6944 commit a4fa281
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 87 deletions.
23 changes: 12 additions & 11 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

const (
version = "0.9.1"
version = "0.9.2-dev"
defaultListenAddr = "127.0.0.1:7777"
)

Expand Down Expand Up @@ -175,7 +175,7 @@ func (p proxyParser) ProxySocks5(val string) {
if err := checkServerAddr(val); err != nil {
Fatal("parent socks server", err)
}
addParentProxy(newSocksParent(val))
parentProxy.add(newSocksParent(val))
}

func (pp proxyParser) ProxyHttp(val string) {
Expand All @@ -199,7 +199,7 @@ func (pp proxyParser) ProxyHttp(val string) {

parent := newHttpParent(server)
parent.initAuth(userPasswd)
addParentProxy(parent)
parentProxy.add(parent)
}

// Parse method:passwd@server:port
Expand Down Expand Up @@ -237,7 +237,7 @@ func (pp proxyParser) ProxySs(val string) {
}
parent := newShadowsocksParent(server)
parent.initCipher(method, passwd)
addParentProxy(parent)
parentProxy.add(parent)
}

func (pp proxyParser) ProxyCow(val string) {
Expand All @@ -260,7 +260,7 @@ func (pp proxyParser) ProxyCow(val string) {
}
config.saveReqLine = true
parent := newCowParent(server, arr[0], arr[1])
addParentProxy(parent)
parentProxy.add(parent)
}

// listenParser provides functions to parse different types of listen addresses
Expand Down Expand Up @@ -420,7 +420,7 @@ func (p configParser) ParseHttpParent(val string) {
}
config.saveReqLine = true
http.parent = newHttpParent(val)
addParentProxy(http.parent)
parentProxy.add(http.parent)
http.serverCnt++
configNeedUpgrade = true
}
Expand Down Expand Up @@ -482,7 +482,7 @@ func (p configParser) ParseShadowSocks(val string) {
Fatal("shadowsocks server", err)
}
shadow.parent = newShadowsocksParent(val)
addParentProxy(shadow.parent)
parentProxy.add(shadow.parent)
shadow.serverCnt++
configNeedUpgrade = true
}
Expand Down Expand Up @@ -659,7 +659,11 @@ func upgradeConfig(rc string, lines []string) {
// comment out original
w.WriteString("#" + line + newLine)
case "httpParent", "shadowSocks", "socksParent":
parent := parentProxy[proxyId]
backPool, ok := parentProxy.(*backupParentPool)
if !ok {
panic("initial parent pool should be backup pool")
}
parent := backPool.parent[proxyId]
proxyId++
w.WriteString(parent.genConfig() + newLine)
// comment out original
Expand Down Expand Up @@ -722,9 +726,6 @@ func checkConfig() {
if listenProxy == nil {
listenProxy = []Proxy{newHttpProxy(defaultListenAddr, "")}
}
if len(parentProxy) <= 1 {
config.LoadBalance = loadBalanceBackup
}
}

func mkConfigDir() (err error) {
Expand Down
14 changes: 8 additions & 6 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ func TestTunnelAllowedPort(t *testing.T) {
}

func TestParseProxy(t *testing.T) {
parentProxy = nil
var ok bool
pool, ok := parentProxy.(*backupParentPool)
if !ok {
t.Fatal("parentPool by default should be backup pool")
}
cnt := -1

var parser configParser
parser.ParseProxy("http://127.0.0.1:8080")
cnt++

hp, ok := parentProxy[cnt].proxyConnector.(*httpParent)
hp, ok := pool.parent[cnt].ParentProxy.(*httpParent)
if !ok {
t.Fatal("1st http proxy parsed not as httpParent")
}
Expand All @@ -71,7 +73,7 @@ func TestParseProxy(t *testing.T) {

parser.ParseProxy("http://user:[email protected]:9090")
cnt++
hp, ok = parentProxy[cnt].proxyConnector.(*httpParent)
hp, ok = pool.parent[cnt].ParentProxy.(*httpParent)
if !ok {
t.Fatal("2nd http proxy parsed not as httpParent")
}
Expand All @@ -84,7 +86,7 @@ func TestParseProxy(t *testing.T) {

parser.ParseProxy("socks5://127.0.0.1:1080")
cnt++
sp, ok := parentProxy[cnt].proxyConnector.(*socksParent)
sp, ok := pool.parent[cnt].ParentProxy.(*socksParent)
if !ok {
t.Fatal("socks proxy parsed not as socksParent")
}
Expand All @@ -94,7 +96,7 @@ func TestParseProxy(t *testing.T) {

parser.ParseProxy("ss://aes-256-cfb:[email protected]:1080")
cnt++
_, ok = parentProxy[cnt].proxyConnector.(*shadowsocksParent)
_, ok = pool.parent[cnt].ParentProxy.(*shadowsocksParent)
if !ok {
t.Fatal("shadowsocks proxy parsed not as shadowsocksParent")
}
Expand Down
7 changes: 1 addition & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,7 @@ func main() {

initStat()

if !hasParentProxy() {
info.Println("no parent proxy server")
}
if debug {
printParentProxy()
}
initParentPool()

/*
if *cpuprofile != "" {
Expand Down
2 changes: 1 addition & 1 deletion pac.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func initPAC() {
func sendPAC(c *clientConn) error {
_, err := c.Write(genPAC(c))
if err != nil {
debug.Printf("cli(%s) error sending PAC:", c.RemoteAddr(), err)
debug.Printf("cli(%s) error sending PAC: %s", c.RemoteAddr(), err)
}
return err
}
164 changes: 108 additions & 56 deletions parent_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,89 +12,141 @@ import (
"strconv"
)

func connectByParentProxy(url *URL) (srvconn net.Conn, err error) {
const baseFailCnt = 9
var skipped []int
nproxy := len(parentProxy)
// Interface that all types of parent proxies should support.
type ParentProxy interface {
connect(*URL) (net.Conn, error)
genConfig() string // for upgrading config
}

firstId := 0
if config.LoadBalance == loadBalanceHash {
firstId = int(stringHash(url.Host) % uint64(nproxy))
debug.Println("use proxy", firstId)
}
// Interface for different proxy selection strategy.
type ParentPool interface {
add(ParentProxy)
empty() bool
// Select a proxy from the pool and connect. May try several proxies until
// one that succees, return nil and error if all parent proxies fail.
connect(*URL) (net.Conn, error)
}

for i := 0; i < nproxy; i++ {
proxyId := (firstId + i) % nproxy
pp := &parentProxy[proxyId]
// skip failed server, but try it with some probability
if pp.failCnt > 0 && rand.Intn(pp.failCnt+baseFailCnt) != 0 {
skipped = append(skipped, proxyId)
continue
}
if srvconn, err = pp.connect(url); err == nil {
return
}
// Init parentProxy to be backup pool. So config parsing have a pool to add
// parent proxies.
var parentProxy ParentPool = &backupParentPool{}

func initParentPool() {
backPool, ok := parentProxy.(*backupParentPool)
if !ok {
panic("initial parent pool should be backup pool")
}
// last resort, try skipped one, not likely to succeed
for _, skippedId := range skipped {
if srvconn, err = parentProxy[skippedId].connect(url); err == nil {
return
}
if debug {
printParentProxy(backPool.parent)
}
if len(parentProxy) != 0 {
if len(backPool.parent) == 0 {
info.Println("no parent proxy server")
return
}
return nil, errors.New("no parent proxy")
if len(backPool.parent) == 1 && config.LoadBalance != loadBalanceBackup {
debug.Println("only 1 parent, no need for load balance")
config.LoadBalance = loadBalanceBackup
}

switch config.LoadBalance {
case loadBalanceHash:
debug.Println("hash parent pool", len(backPool.parent))
parentProxy = &hashParentPool{*backPool}
}
}

// proxyConnector is the interface that all parent proxies should support.
type proxyConnector interface {
connect(*URL) (net.Conn, error)
genConfig() string // for upgrading config
func printParentProxy(parent []ParentWithFail) {
debug.Println("avaiable parent proxies:")
for _, pp := range parent {
switch pc := pp.ParentProxy.(type) {
case *shadowsocksParent:
debug.Println("\tshadowsocks: ", pc.server)
case *httpParent:
debug.Println("\thttp parent: ", pc.server)
case *socksParent:
debug.Println("\tsocks parent: ", pc.server)
case *cowParent:
debug.Println("\tcow parent: ", pc.server)
}
}
}

type ParentProxy struct {
proxyConnector
failCnt int
type ParentWithFail struct {
ParentProxy
fail int
}

var parentProxy []ParentProxy
// Backup load balance strategy:
// Select proxy in the order they appear in config.
type backupParentPool struct {
parent []ParentWithFail
}

func (pp *backupParentPool) empty() bool {
return len(pp.parent) == 0
}

func (pp *backupParentPool) add(parent ParentProxy) {
pp.parent = append(pp.parent, ParentWithFail{parent, 0})
}

func (pp *backupParentPool) connect(url *URL) (srvconn net.Conn, err error) {
return connectInOrder(url, pp.parent, 0)
}

func hasParentProxy() bool {
return len(parentProxy) != 0
// Hash load balance strategy:
// Each host will use a proxy based on a hash value.
type hashParentPool struct {
backupParentPool
}

func addParentProxy(pc proxyConnector) {
parentProxy = append(parentProxy, ParentProxy{pc, 0})
func (pp *hashParentPool) connect(url *URL) (srvconn net.Conn, err error) {
start := int(stringHash(url.Host) % uint64(len(pp.parent)))
debug.Printf("hash host %s try %d parent first", url.Host, start)
return connectInOrder(url, pp.parent, start)
}

func (pp *ParentProxy) connect(url *URL) (srvconn net.Conn, err error) {
func (parent *ParentWithFail) connect(url *URL) (srvconn net.Conn, err error) {
const maxFailCnt = 30
srvconn, err = pp.proxyConnector.connect(url)
srvconn, err = parent.ParentProxy.connect(url)
if err != nil {
if pp.failCnt < maxFailCnt && !networkBad() {
pp.failCnt++
if parent.fail < maxFailCnt && !networkBad() {
parent.fail++
}
return
}
pp.failCnt = 0
parent.fail = 0
return
}

func printParentProxy() {
debug.Println("avaiable parent proxies:")
for _, pp := range parentProxy {
switch pc := pp.proxyConnector.(type) {
case *shadowsocksParent:
debug.Println("\tshadowsocks: ", pc.server)
case *httpParent:
debug.Println("\thttp parent: ", pc.server)
case *socksParent:
debug.Println("\tsocks parent: ", pc.server)
case *cowParent:
debug.Println("\tcow parent: ", pc.server)
func connectInOrder(url *URL, pp []ParentWithFail, start int) (srvconn net.Conn, err error) {
const baseFailCnt = 9
var skipped []int
nproxy := len(pp)

if nproxy == 0 {
return nil, errors.New("no parent proxy")
}

for i := 0; i < nproxy; i++ {
proxyId := (start + i) % nproxy
parent := &pp[proxyId]
// skip failed server, but try it with some probability
if parent.fail > 0 && rand.Intn(parent.fail+baseFailCnt) != 0 {
skipped = append(skipped, proxyId)
continue
}
if srvconn, err = parent.connect(url); err == nil {
return
}
}
// last resort, try skipped one, not likely to succeed
for _, skippedId := range skipped {
if srvconn, err = pp[skippedId].connect(url); err == nil {
return
}
}
return nil, err
}

// http parent proxy
Expand Down
Loading

0 comments on commit a4fa281

Please sign in to comment.