Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
proxy
|
||||
*.exe
|
||||
*.exe~
|
||||
release-*
|
||||
proxy.crt
|
||||
proxy.key
|
||||
23
CHANGELOG
Normal file
23
CHANGELOG
Normal file
@ -0,0 +1,23 @@
|
||||
proxy更新日志:
|
||||
v2.2
|
||||
1.增加了强制使用上级代理参数always.可以使所有流量都走上级代理.
|
||||
2.增加了定时检查网络是否正常,可以在本地网络不稳定的时候修复连接池状态,提升代理访问体验.
|
||||
3.http代理增加了对ipv6地址的支持。
|
||||
|
||||
v2.1
|
||||
1.增加了http basic验证功能,可以对http代理协议设置basic验证,用户名和密码支持来自文件或者命令行.
|
||||
2.优化了域名检查方法,避免空连接的出现。
|
||||
3.修复了连接上级代理超时参数传递错误导致超时过大的问题.
|
||||
4.增加了连接池状态监测,如果上级代理或者网络出现问题,会及时重新初始化连接池,防止大量无效连接,降低浏览体验.
|
||||
5.增加了对系统kill信号的捕获,可以在收到系统kill信号之后执行清理释放连接的操作.避免出现大量CLOSE_WAIT.
|
||||
|
||||
v2.0
|
||||
1.增加了连接池功能,大幅提高了通过上级代理访问的速度.
|
||||
2.HTTP代理模式,优化了请求URL的获取逻辑,可以支持:http,https,websocket
|
||||
3.增加了TCP代理模式,支持是否加密通讯.
|
||||
4.优化了链接关闭逻辑,避免出现大量CLOSE_WAIT.
|
||||
5.增加了黑白名单机制,更自由快速的访问.
|
||||
6.优化了网站Block机制检测,判断更准确.
|
||||
|
||||
v1.0
|
||||
1.始发版本,可以代理http,https.
|
||||
115
config.go
Executable file
115
config.go
Executable file
@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg = viper.New()
|
||||
)
|
||||
|
||||
func initConfig() (err error) {
|
||||
//define command line args
|
||||
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
configFile := pflag.StringP("config", "c", "", "config file path")
|
||||
|
||||
pflag.BoolP("parent-tls", "X", false, "parent proxy is tls")
|
||||
pflag.BoolP("local-tls", "x", false, "local proxy is tls")
|
||||
version := pflag.BoolP("version", "v", false, "show version")
|
||||
pflag.BoolP("tcp", "C", false, "proxy on tcp")
|
||||
pflag.Bool("always", false, "always use parent proxy")
|
||||
|
||||
pflag.Int("check-proxy-interval", 3, "check if proxy is okay every interval seconds")
|
||||
pflag.IntP("port", "p", 33080, "local port to listen")
|
||||
pflag.IntP("check-timeout", "t", 3000, "chekc domain blocked , http request timeout milliseconds when connect to host")
|
||||
pflag.IntP("tcp-timeout", "T", 2000, "tcp timeout milliseconds when connect to real server or parent proxy")
|
||||
pflag.IntP("check-interval", "I", 10, "check domain if blocked every interval seconds")
|
||||
pflag.IntP("pool-size", "s", 50, "conn pool size , which connect to parent proxy, zero: means turn off pool")
|
||||
|
||||
pflag.StringP("parent", "P", "", "parent proxy address")
|
||||
pflag.StringP("ip", "i", "0.0.0.0", "local ip to bind")
|
||||
pflag.StringP("cert", "f", "proxy.crt", "cert file for tls")
|
||||
pflag.StringP("key", "k", "proxy.key", "key file for tls")
|
||||
pflag.StringP("blocked", "b", "blocked", "blocked domain file , one domain each line")
|
||||
pflag.StringP("direct", "d", "direct", "direct domain file , one domain each line")
|
||||
pflag.StringP("auth-file", "F", "", "http basic auth file,\"username:password\" each line in file")
|
||||
pflag.StringSliceP("auth", "a", []string{}, "http basic auth username and password,such as: \"user1:pass1,user2:pass2\"")
|
||||
|
||||
pflag.Parse()
|
||||
|
||||
cfg.BindPFlag("parent-tls", pflag.Lookup("parent-tls"))
|
||||
cfg.BindPFlag("local-tls", pflag.Lookup("local-tls"))
|
||||
cfg.BindPFlag("tcp", pflag.Lookup("tcp"))
|
||||
cfg.BindPFlag("always", pflag.Lookup("always"))
|
||||
cfg.BindPFlag("check-proxy-interval", pflag.Lookup("check-proxy-interval"))
|
||||
cfg.BindPFlag("port", pflag.Lookup("port"))
|
||||
cfg.BindPFlag("check-timeout", pflag.Lookup("check-timeout"))
|
||||
cfg.BindPFlag("tcp-timeout", pflag.Lookup("tcp-timeout"))
|
||||
cfg.BindPFlag("check-interval", pflag.Lookup("check-interval"))
|
||||
cfg.BindPFlag("pool-size", pflag.Lookup("pool-size"))
|
||||
cfg.BindPFlag("parent", pflag.Lookup("parent"))
|
||||
cfg.BindPFlag("ip", pflag.Lookup("ip"))
|
||||
cfg.BindPFlag("cert", pflag.Lookup("cert"))
|
||||
cfg.BindPFlag("key", pflag.Lookup("key"))
|
||||
cfg.BindPFlag("blocked", pflag.Lookup("blocked"))
|
||||
cfg.BindPFlag("direct", pflag.Lookup("direct"))
|
||||
cfg.BindPFlag("auth", pflag.Lookup("auth"))
|
||||
cfg.BindPFlag("auth-file", pflag.Lookup("auth-file"))
|
||||
|
||||
//version
|
||||
if *version {
|
||||
fmt.Printf("proxy v%s\n", APP_VERSION)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
//keygen
|
||||
if len(pflag.Args()) > 0 {
|
||||
if pflag.Arg(0) == "keygen" {
|
||||
keygen()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
poster()
|
||||
|
||||
if *configFile != "" {
|
||||
cfg.SetConfigFile(*configFile)
|
||||
} else {
|
||||
cfg.SetConfigName("proxy")
|
||||
cfg.AddConfigPath("/etc/proxy/")
|
||||
cfg.AddConfigPath("$HOME/.proxy")
|
||||
cfg.AddConfigPath(".proxy")
|
||||
cfg.AddConfigPath(".")
|
||||
}
|
||||
|
||||
err = cfg.ReadInConfig()
|
||||
file := cfg.ConfigFileUsed()
|
||||
if err != nil && !strings.Contains(err.Error(), "Not") {
|
||||
log.Fatalf("parse config fail, ERR:%s", err)
|
||||
} else if file != "" {
|
||||
log.Printf("use config file : %s", file)
|
||||
}
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
func poster() {
|
||||
fmt.Printf(`
|
||||
######## ######## ####### ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ####
|
||||
######## ######## ## ## ### ##
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ####### ## ## ##
|
||||
|
||||
v%s`+" by snail , blog : http://www.host900.com/\n\n", APP_VERSION)
|
||||
}
|
||||
456
direct
Normal file
456
direct
Normal file
@ -0,0 +1,456 @@
|
||||
07073.com
|
||||
10010.com
|
||||
100ye.com
|
||||
114la.com
|
||||
115.com
|
||||
120ask.com
|
||||
126.com
|
||||
126.net
|
||||
1616.net
|
||||
163.com
|
||||
17173.com
|
||||
1778.com
|
||||
178.com
|
||||
17u.com
|
||||
19lou.com
|
||||
1o26.com
|
||||
1ting.com
|
||||
21cn.com
|
||||
2345.com
|
||||
265.com
|
||||
265g.com
|
||||
28.com
|
||||
28tui.com
|
||||
2hua.com
|
||||
2mdn.net
|
||||
315che.com
|
||||
3366.com
|
||||
360buy.com
|
||||
360buyimg.com
|
||||
360doc.com
|
||||
36kr.com
|
||||
39.net
|
||||
3dmgame.com
|
||||
4399.com
|
||||
4738.com
|
||||
500wan.com
|
||||
51.com
|
||||
51.la
|
||||
5173.com
|
||||
51auto.com
|
||||
51buy.com
|
||||
51cto.com
|
||||
51fanli.com
|
||||
51job.com
|
||||
52kmh.com
|
||||
52pk.net
|
||||
52tlbb.com
|
||||
53kf.com
|
||||
55bbs.com
|
||||
55tuan.com
|
||||
56.com
|
||||
58.com
|
||||
591hx.com
|
||||
5d6d.net
|
||||
61.com
|
||||
70e.com
|
||||
777wyx.com
|
||||
778669.com
|
||||
7c.com
|
||||
7k7k.com
|
||||
88db.com
|
||||
91.com
|
||||
99bill.com
|
||||
a135.net
|
||||
abang.com
|
||||
abchina.com
|
||||
ad1111.com
|
||||
admin5.com
|
||||
adnxs.com
|
||||
adobe.com
|
||||
adroll.com
|
||||
ads8.com
|
||||
adsame.com
|
||||
adsonar.com
|
||||
adtechus.com
|
||||
aibang.com
|
||||
aifang.com
|
||||
aili.com
|
||||
aipai.com
|
||||
aizhan.com
|
||||
ali213.net
|
||||
alibaba.com
|
||||
alicdn.com
|
||||
aliexpress.com
|
||||
alimama.com
|
||||
alipay.com
|
||||
alipayobjects.com
|
||||
alisoft.com
|
||||
alivv.com
|
||||
aliyun.com
|
||||
allyes.com
|
||||
amazon.com
|
||||
anjuke.com
|
||||
anzhi.com
|
||||
aol.com
|
||||
apple.com
|
||||
arpg2.com
|
||||
atdmt.com
|
||||
b2b168.com
|
||||
babytree.com
|
||||
baidu.com
|
||||
baihe.com
|
||||
baixing.com
|
||||
bankcomm.com
|
||||
baomihua.com
|
||||
bdimg.com
|
||||
bdstatic.com
|
||||
bendibao.com
|
||||
betrad.com
|
||||
bilibili.tv
|
||||
bing.com
|
||||
bitauto.com
|
||||
blog.163.com
|
||||
blogchina.com
|
||||
blueidea.com
|
||||
bluekai.com
|
||||
booksky.org
|
||||
caixin.com
|
||||
ccb.com
|
||||
ccidnet.com
|
||||
cctv*.com
|
||||
china.com
|
||||
chinabyte.com
|
||||
chinahr.com
|
||||
chinanews.com
|
||||
chinaw3.com
|
||||
chinaz.com
|
||||
chuangelm.com
|
||||
ci123.com
|
||||
cmbchina.com
|
||||
cnbeta.com
|
||||
cnblogs.com
|
||||
cncn.com
|
||||
cnhubei.com
|
||||
cnki.net
|
||||
cnmo.com
|
||||
cnxad.com
|
||||
cnzz.com
|
||||
cocoren.com
|
||||
compete.com
|
||||
comsenz.com
|
||||
coo8.com
|
||||
cqnews.net
|
||||
crsky.com
|
||||
csdn.net
|
||||
ct10000.com
|
||||
ctrip.com
|
||||
dangdang.com
|
||||
daqi.com
|
||||
dayoo.com
|
||||
dbank.com
|
||||
ddmap.com
|
||||
dedecms.com
|
||||
dh818.com
|
||||
diandian.com
|
||||
dianping.com
|
||||
discuz.net
|
||||
doc88.com
|
||||
docin.com
|
||||
donews.com
|
||||
dospy.com
|
||||
douban.com
|
||||
douban.fm
|
||||
doubleclick.com
|
||||
doubleclick.net
|
||||
duba.net
|
||||
duote.com
|
||||
duowan.com
|
||||
dzwww.com
|
||||
eastday.com
|
||||
eastmoney.com
|
||||
ebay.com
|
||||
elong.com
|
||||
ename.net
|
||||
etao.com
|
||||
exam8.com
|
||||
eye.rs
|
||||
fantong.com
|
||||
fastcdn.com
|
||||
fblife.com
|
||||
fengniao.com
|
||||
fenzhi.com
|
||||
flickr.com
|
||||
fobshanghai.com
|
||||
ftuan.com
|
||||
funshion.com
|
||||
fx120.net
|
||||
game3737.com
|
||||
gamersky.com
|
||||
gamestlbb.com
|
||||
gamesville.com
|
||||
ganji.com
|
||||
gfan.com
|
||||
gongchang.com
|
||||
google-analytics.com
|
||||
gougou.com
|
||||
gtimg.com
|
||||
hao123.com
|
||||
haodf.com
|
||||
harrenmedianetwork.com
|
||||
hc360.com
|
||||
hefei.cc
|
||||
hf365.com
|
||||
hiapk.com
|
||||
hichina.com
|
||||
homeinns.com
|
||||
hotsales.net
|
||||
house365.com
|
||||
huaban.com
|
||||
huanqiu.com
|
||||
hudong.com
|
||||
hupu.com
|
||||
iask.com
|
||||
iciba.com
|
||||
icson.com
|
||||
ifeng.com
|
||||
iloveyouxi.com
|
||||
im286.com
|
||||
imanhua.com
|
||||
img.cctvpic.com
|
||||
imrworldwide.com
|
||||
invitemedia.com
|
||||
ip138.com
|
||||
ipinyou.com
|
||||
iqilu.com
|
||||
iqiyi.com
|
||||
irs01.com
|
||||
irs01.net
|
||||
it168.com
|
||||
iteye.com
|
||||
iyaya.com
|
||||
jb51.net
|
||||
jiathis.com
|
||||
jiayuan.com
|
||||
jing.fm
|
||||
jinti.com
|
||||
jqw.com
|
||||
jumei.com
|
||||
jxedt.com
|
||||
jysq.net
|
||||
kaixin001.com
|
||||
kandian.com
|
||||
kdnet.net
|
||||
kimiss.com
|
||||
ku6.com
|
||||
ku6cdn.com
|
||||
ku6img.com
|
||||
kuaidi100.com
|
||||
kugou.com
|
||||
l99.com
|
||||
lady8844.com
|
||||
lafaso.com
|
||||
lashou.com
|
||||
legolas-media.com
|
||||
lehecai.com
|
||||
leho.com
|
||||
letv.com
|
||||
liebiao.com
|
||||
lietou.com
|
||||
linezing.com
|
||||
linkedin.com
|
||||
live.com
|
||||
longhoo.net
|
||||
lusongsong.com
|
||||
lxdns.com
|
||||
lycos.com
|
||||
lygo.com
|
||||
m18.com
|
||||
m1905.com
|
||||
made-in-china.com
|
||||
makepolo.com
|
||||
mangocity.com
|
||||
manzuo.com
|
||||
mapbar.com
|
||||
mathtag.com
|
||||
mediaplex.com
|
||||
mediav.com
|
||||
meilele.com
|
||||
meilishuo.com
|
||||
meishichina.com
|
||||
meituan.com
|
||||
meizu.com
|
||||
miaozhen.com
|
||||
microsoft.com
|
||||
miercn.com
|
||||
mlt01.com
|
||||
mmcdn.cn
|
||||
mmstat.com
|
||||
mnwan.com
|
||||
mogujie.com
|
||||
mookie1.com
|
||||
moonbasa.com
|
||||
mop.com
|
||||
mosso.com
|
||||
mplife.com
|
||||
msn.com
|
||||
mtime.com
|
||||
mumayi.com
|
||||
mydrivers.com
|
||||
net114.com
|
||||
netease.com
|
||||
newsmth.net
|
||||
nipic.com
|
||||
nowec.com
|
||||
nuomi.com
|
||||
oadz.com
|
||||
oeeee.com
|
||||
onetad.com
|
||||
onlinedown.net
|
||||
onlylady.com
|
||||
oschina.net
|
||||
otwan.com
|
||||
paipai.com
|
||||
paypal.com
|
||||
pchome.net
|
||||
pcpop.com
|
||||
pengyou.com
|
||||
php100.com
|
||||
phpwind.net
|
||||
pingan.com
|
||||
pixlr.com
|
||||
pp.cc
|
||||
ppstream.com
|
||||
pptv.com
|
||||
ptlogin2.qq.com
|
||||
pubmatic.com
|
||||
q150.com
|
||||
qianlong.com
|
||||
qidian.com
|
||||
qingdaonews.com
|
||||
qire123.com
|
||||
qiushibaike.com
|
||||
qiyou.com
|
||||
qjy168.com
|
||||
qq.com
|
||||
qq937.com
|
||||
qstatic.com
|
||||
quantserve.com
|
||||
qunar.com
|
||||
rakuten.co.jp
|
||||
readnovel.com
|
||||
renren.com
|
||||
rtbidder.net
|
||||
scanscout.com
|
||||
scorecardresearch.com
|
||||
sdo.com
|
||||
seowhy.com
|
||||
serving-sys.com
|
||||
sf-express.com
|
||||
shangdu.com
|
||||
si.kz
|
||||
sina.com
|
||||
sinahk.net
|
||||
sinajs.com
|
||||
smzdm.com
|
||||
snyu.com
|
||||
sodu.org
|
||||
sogou.com
|
||||
sohu.com
|
||||
soku.com
|
||||
sootoo.com
|
||||
soso.com
|
||||
soufun.com
|
||||
sourceforge.net
|
||||
staticsdo.com
|
||||
stockstar.com
|
||||
sttlbb.com
|
||||
suning.com
|
||||
szhome.com
|
||||
sznews.com
|
||||
tangdou.com
|
||||
tanx.com
|
||||
tao123.com
|
||||
taobao.com
|
||||
taobaocdn.com
|
||||
tbcdn.cn
|
||||
tdimg.com
|
||||
tenpay.com
|
||||
tgbus.com
|
||||
theplanet.com
|
||||
thethirdmedia.com
|
||||
tiancity.com
|
||||
tianji.com
|
||||
tiao8.info
|
||||
tiexue.net
|
||||
titan24.com
|
||||
tmall.com
|
||||
tom.com
|
||||
toocle.com
|
||||
tremormedia.com
|
||||
tuan800.com
|
||||
tudou.com
|
||||
tudouui.com
|
||||
tui18.com
|
||||
tuniu.com
|
||||
twcczhu.com
|
||||
u17.com
|
||||
ucjoy.com
|
||||
ulink.cc
|
||||
uniontoufang.com
|
||||
up2c.com
|
||||
uuu9.com
|
||||
uuzu.com
|
||||
vancl.com
|
||||
verycd.com
|
||||
vipshop.com
|
||||
vizu.com
|
||||
vjia.com
|
||||
weibo.com
|
||||
weiphone.com
|
||||
west263.com
|
||||
whlongda.com
|
||||
wrating.com
|
||||
wumii.com
|
||||
xiami.com
|
||||
xiaomi.com
|
||||
xiazaiba.com
|
||||
xici.net
|
||||
xinhuanet.com
|
||||
xinnet.com
|
||||
xitek.com
|
||||
xiu.com
|
||||
xunlei.com
|
||||
xyxy.net
|
||||
yahoo.co.jp
|
||||
yahoo.com
|
||||
yaolan.com
|
||||
yesky.com
|
||||
yieldmanager.com
|
||||
yihaodian.com
|
||||
yingjiesheng.com
|
||||
yinyuetai.com
|
||||
yiqifa.com
|
||||
ykimg.com
|
||||
ynet.com
|
||||
yoka.com
|
||||
yolk7.com
|
||||
youboy.com
|
||||
youdao.com
|
||||
yougou.com
|
||||
youku.com
|
||||
youshang.com
|
||||
yupoo.com
|
||||
yxlady.com
|
||||
yyets.com
|
||||
zhaodao123.com
|
||||
zhaopin.com
|
||||
zhenai.com
|
||||
zhibo8.cc
|
||||
zhihu.com
|
||||
zhubajie.com
|
||||
zongheng.com
|
||||
zoosnet.net
|
||||
zqgame.com
|
||||
ztgame.com
|
||||
zx915.com
|
||||
346
functions.go
Executable file
346
functions.go
Executable file
@ -0,0 +1,346 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func IoBind(dst io.ReadWriter, src io.ReadWriter, fn func(err error), cfn func(count int, isPositive bool), bytesPreSec float64) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
errchn := make(chan error, 2)
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
if bytesPreSec > 0 {
|
||||
newreader := NewReader(src)
|
||||
newreader.SetRateLimit(bytesPreSec)
|
||||
_, err = ioCopy(dst, newreader, func(c int) {
|
||||
cfn(c, false)
|
||||
})
|
||||
|
||||
} else {
|
||||
_, err = ioCopy(dst, src, func(c int) {
|
||||
cfn(c, false)
|
||||
})
|
||||
}
|
||||
errchn <- err
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
if bytesPreSec > 0 {
|
||||
newReader := NewReader(dst)
|
||||
newReader.SetRateLimit(bytesPreSec)
|
||||
_, err = ioCopy(src, newReader, func(c int) {
|
||||
cfn(c, true)
|
||||
})
|
||||
} else {
|
||||
_, err = ioCopy(src, dst, func(c int) {
|
||||
cfn(c, true)
|
||||
})
|
||||
}
|
||||
errchn <- err
|
||||
}()
|
||||
fn(<-errchn)
|
||||
}()
|
||||
}
|
||||
func ioCopy(dst io.Writer, src io.Reader, fn ...func(count int)) (written int64, err error) {
|
||||
buf := make([]byte, 32*1024)
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
if len(fn) == 1 {
|
||||
fn[0](nw)
|
||||
}
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er != nil {
|
||||
err = er
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
func TlsConnectHost(host string, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) {
|
||||
h := strings.Split(host, ":")
|
||||
port, _ := strconv.Atoi(h[1])
|
||||
return TlsConnect(h[0], port, timeout, certBytes, keyBytes)
|
||||
}
|
||||
|
||||
func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) {
|
||||
conf, err := getRequestTlsConfig(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Duration(timeout)*time.Millisecond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return *tls.Client(_conn, conf), err
|
||||
}
|
||||
func getRequestTlsConfig(certBytes, keyBytes []byte) (conf *tls.Config, err error) {
|
||||
var cert tls.Certificate
|
||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
serverCertPool := x509.NewCertPool()
|
||||
ok := serverCertPool.AppendCertsFromPEM(certBytes)
|
||||
if !ok {
|
||||
err = errors.New("failed to parse root certificate")
|
||||
}
|
||||
conf = &tls.Config{
|
||||
RootCAs: serverCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ServerName: "proxy",
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectHost(hostAndPort string, timeout int) (conn net.Conn, err error) {
|
||||
conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond)
|
||||
return
|
||||
}
|
||||
func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) {
|
||||
var cert tls.Certificate
|
||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
clientCertPool := x509.NewCertPool()
|
||||
ok := clientCertPool.AppendCertsFromPEM(certBytes)
|
||||
if !ok {
|
||||
err = errors.New("failed to parse root certificate")
|
||||
}
|
||||
config := &tls.Config{
|
||||
ClientCAs: clientCertPool,
|
||||
ServerName: "proxy",
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
}
|
||||
_ln, err := tls.Listen("tcp", fmt.Sprintf("%s:%d", ip, port), config)
|
||||
if err == nil {
|
||||
ln = &_ln
|
||||
}
|
||||
return
|
||||
}
|
||||
func PathExists(_path string) bool {
|
||||
_, err := os.Stat(_path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func HTTPGet(URL string, timeout int) (err error) {
|
||||
tr := &http.Transport{}
|
||||
var resp *http.Response
|
||||
var client *http.Client
|
||||
defer func() {
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
tr.CloseIdleConnections()
|
||||
}()
|
||||
client = &http.Client{Timeout: time.Millisecond * time.Duration(timeout), Transport: tr}
|
||||
resp, err = client.Get(URL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func initOutPool(isTLS bool, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) {
|
||||
outPool, err = NewConnPool(poolConfig{
|
||||
IsActive: func(conn interface{}) bool { return true },
|
||||
Release: func(conn interface{}) {
|
||||
if conn != nil {
|
||||
conn.(net.Conn).SetDeadline(time.Now().Add(time.Millisecond))
|
||||
conn.(net.Conn).Close()
|
||||
// log.Println("conn released")
|
||||
}
|
||||
},
|
||||
InitialCap: InitialCap,
|
||||
MaxCap: MaxCap,
|
||||
Factory: func() (conn interface{}, err error) {
|
||||
conn, err = getConn(isTLS, certBytes, keyBytes, address, timeout)
|
||||
return
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("init conn pool fail ,%s", err)
|
||||
} else {
|
||||
log.Printf("init conn pool success")
|
||||
initPoolDeamon(isTLS, certBytes, keyBytes, address, timeout)
|
||||
}
|
||||
}
|
||||
func getConn(isTLS bool, certBytes, keyBytes []byte, address string, timeout int) (conn interface{}, err error) {
|
||||
if isTLS {
|
||||
var _conn tls.Conn
|
||||
_conn, err = TlsConnectHost(address, timeout, certBytes, keyBytes)
|
||||
if err == nil {
|
||||
conn = net.Conn(&_conn)
|
||||
}
|
||||
} else {
|
||||
conn, err = ConnectHost(address, timeout)
|
||||
}
|
||||
return
|
||||
}
|
||||
func initPoolDeamon(isTLS bool, certBytes, keyBytes []byte, address string, timeout int) {
|
||||
go func() {
|
||||
dur := cfg.GetInt("check-proxy-interval")
|
||||
if dur <= 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("pool deamon started")
|
||||
for {
|
||||
time.Sleep(time.Second * time.Duration(dur))
|
||||
conn, err := getConn(isTLS, certBytes, keyBytes, address, timeout)
|
||||
if err != nil {
|
||||
log.Printf("pool deamon err %s , release pool", err)
|
||||
outPool.ReleaseAll()
|
||||
} else {
|
||||
conn.(net.Conn).SetDeadline(time.Now().Add(time.Millisecond))
|
||||
conn.(net.Conn).Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
func getURL(header []byte, host string) (URL string, err error) {
|
||||
if !strings.HasPrefix(host, "/") {
|
||||
return host, nil
|
||||
}
|
||||
_host, err := getHeader("host", header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
URL = fmt.Sprintf("http://%s%s", _host, host)
|
||||
return
|
||||
}
|
||||
func getHeader(key string, headData []byte) (val string, err error) {
|
||||
key = strings.ToUpper(key)
|
||||
lines := strings.Split(string(headData), "\r\n")
|
||||
for _, line := range lines {
|
||||
line := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2)
|
||||
if len(line) == 2 {
|
||||
k := strings.ToUpper(strings.Trim(line[0], " "))
|
||||
v := strings.Trim(line[1], " ")
|
||||
if key == k {
|
||||
val = v
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("can not find HOST header")
|
||||
return
|
||||
}
|
||||
func hostIsNoPort(host string) bool {
|
||||
//host: [dd:dafds:fsd:dasd:2.2.23.3] or 2.2.23.3 or [dd:dafds:fsd:dasd:2.2.23.3]:2323 or 2.2.23.3:1234
|
||||
if strings.HasPrefix(host, "[") {
|
||||
return strings.HasSuffix(host, "]")
|
||||
}
|
||||
return strings.Index(host, ":") == -1
|
||||
}
|
||||
func fixHost(host string) string {
|
||||
if !strings.HasPrefix(host, "[") && len(strings.Split(host, ":")) > 2 {
|
||||
if strings.HasSuffix(host, ":80") {
|
||||
return fmt.Sprintf("[%s]:80", host[:strings.LastIndex(host, ":")])
|
||||
}
|
||||
if strings.HasSuffix(host, ":443") {
|
||||
return fmt.Sprintf("[%s]:443", host[:strings.LastIndex(host, ":")])
|
||||
}
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
type sockaddr struct {
|
||||
family uint16
|
||||
data [14]byte
|
||||
}
|
||||
|
||||
// const SO_ORIGINAL_DST = 80
|
||||
|
||||
// realServerAddress returns an intercepted connection's original destination.
|
||||
// func realServerAddress(conn *net.Conn) (string, error) {
|
||||
// tcpConn, ok := (*conn).(*net.TCPConn)
|
||||
// if !ok {
|
||||
// return "", errors.New("not a TCPConn")
|
||||
// }
|
||||
|
||||
// file, err := tcpConn.File()
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// // To avoid potential problems from making the socket non-blocking.
|
||||
// tcpConn.Close()
|
||||
// *conn, err = net.FileConn(file)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// defer file.Close()
|
||||
// fd := file.Fd()
|
||||
|
||||
// var addr sockaddr
|
||||
// size := uint32(unsafe.Sizeof(addr))
|
||||
// err = getsockopt(int(fd), syscall.SOL_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), &size)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// var ip net.IP
|
||||
// switch addr.family {
|
||||
// case syscall.AF_INET:
|
||||
// ip = addr.data[2:6]
|
||||
// default:
|
||||
// return "", errors.New("unrecognized address family")
|
||||
// }
|
||||
|
||||
// port := int(addr.data[0])<<8 + int(addr.data[1])
|
||||
|
||||
// return net.JoinHostPort(ip.String(), strconv.Itoa(port)), nil
|
||||
// }
|
||||
|
||||
// func getsockopt(s int, level int, name int, val uintptr, vallen *uint32) (err error) {
|
||||
// _, _, e1 := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
|
||||
// if e1 != 0 {
|
||||
// err = e1
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
97
io-limiter.go
Normal file
97
io-limiter.go
Normal file
@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const burstLimit = 1000 * 1000 * 1000
|
||||
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
limiter *rate.Limiter
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
limiter *rate.Limiter
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewReader returns a reader that implements io.Reader with rate limiting.
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewReaderWithContext returns a reader that implements io.Reader with rate limiting.
|
||||
func NewReaderWithContext(r io.Reader, ctx context.Context) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWriter returns a writer that implements io.Writer with rate limiting.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewWriterWithContext returns a writer that implements io.Writer with rate limiting.
|
||||
func NewWriterWithContext(w io.Writer, ctx context.Context) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// SetRateLimit sets rate limit (bytes/sec) to the reader.
|
||||
func (s *Reader) SetRateLimit(bytesPerSec float64) {
|
||||
s.limiter = rate.NewLimiter(rate.Limit(bytesPerSec), burstLimit)
|
||||
s.limiter.AllowN(time.Now(), burstLimit) // spend initial burst
|
||||
}
|
||||
|
||||
// Read reads bytes into p.
|
||||
func (s *Reader) Read(p []byte) (int, error) {
|
||||
if s.limiter == nil {
|
||||
return s.r.Read(p)
|
||||
}
|
||||
n, err := s.r.Read(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if err := s.limiter.WaitN(s.ctx, n); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// SetRateLimit sets rate limit (bytes/sec) to the writer.
|
||||
func (s *Writer) SetRateLimit(bytesPerSec float64) {
|
||||
s.limiter = rate.NewLimiter(rate.Limit(bytesPerSec), burstLimit)
|
||||
s.limiter.AllowN(time.Now(), burstLimit) // spend initial burst
|
||||
}
|
||||
|
||||
// Write writes bytes from p.
|
||||
func (s *Writer) Write(p []byte) (int, error) {
|
||||
if s.limiter == nil {
|
||||
return s.w.Write(p)
|
||||
}
|
||||
n, err := s.w.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if err := s.limiter.WaitN(s.ctx, n); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
3
keygen.sh
Executable file
3
keygen.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
openssl genrsa -out proxy.key 2048
|
||||
openssl req -new -key proxy.key -x509 -days 3650 -out proxy.crt -subj /C=CN/ST=BJ/O="Localhost Ltd"/CN=proxy
|
||||
382
main.go
Normal file
382
main.go
Normal file
@ -0,0 +1,382 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const APP_VERSION = "2.2"
|
||||
|
||||
var (
|
||||
checker Checker
|
||||
proxyIsTls bool
|
||||
localIsTls bool
|
||||
proxyAddr string
|
||||
isTCP bool
|
||||
connTimeout int
|
||||
certBytes []byte
|
||||
keyBytes []byte
|
||||
err error
|
||||
outPool ConnPool
|
||||
basicAuth BasicAuth
|
||||
httpAuthorization bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
err = initConfig()
|
||||
if err != nil {
|
||||
log.Printf("err : %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
//catch panic error
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
log.Printf("err : %s,\ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
//define command line args
|
||||
proxyIsTls = cfg.GetBool("parent-tls")
|
||||
localIsTls = cfg.GetBool("local-tls")
|
||||
proxyAddr = cfg.GetString("parent")
|
||||
bindIP := cfg.GetString("ip")
|
||||
bindPort := cfg.GetInt("port")
|
||||
timeout := cfg.GetInt("check-timeout")
|
||||
connTimeout = cfg.GetInt("tcp-timeout")
|
||||
interval := cfg.GetInt("check-interval")
|
||||
certFile := cfg.GetString("cert")
|
||||
keyFile := cfg.GetString("key")
|
||||
blockedFile := cfg.GetString("blocked")
|
||||
directFile := cfg.GetString("direct")
|
||||
|
||||
isTCP = cfg.GetBool("tcp")
|
||||
poolInitSize := cfg.GetInt("pool-size")
|
||||
|
||||
//check args required
|
||||
if proxyIsTls && proxyAddr == "" {
|
||||
log.Fatalf("parent proxy address required")
|
||||
}
|
||||
|
||||
//check tls cert&key file
|
||||
if certFile == "" {
|
||||
certFile = "proxy.crt"
|
||||
}
|
||||
if keyFile == "" {
|
||||
keyFile = "proxy.key"
|
||||
}
|
||||
if proxyIsTls || localIsTls {
|
||||
certBytes, err = ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
log.Printf("err : %s", err)
|
||||
return
|
||||
}
|
||||
keyBytes, err = ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
log.Printf("err : %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
//init tls info string
|
||||
var proxyIsTlsStr string
|
||||
var localIsTlsStr string
|
||||
protocolStr := "tcp"
|
||||
if !isTCP {
|
||||
protocolStr = "http(s)"
|
||||
}
|
||||
if proxyIsTls {
|
||||
proxyIsTlsStr = "tls "
|
||||
}
|
||||
if localIsTls {
|
||||
localIsTlsStr = "tls "
|
||||
}
|
||||
//init checker and pool if needed
|
||||
if proxyAddr != "" {
|
||||
if !isTCP && !cfg.GetBool("always") {
|
||||
checker = NewChecker(timeout, int64(interval), blockedFile, directFile)
|
||||
}
|
||||
log.Printf("use %sparent %s proxy : %s", proxyIsTlsStr, protocolStr, proxyAddr)
|
||||
initOutPool(proxyIsTls, certBytes, keyBytes, proxyAddr, connTimeout, poolInitSize, poolInitSize*2)
|
||||
} else if isTCP {
|
||||
log.Printf("tcp proxy need parent")
|
||||
return
|
||||
}
|
||||
//init basic auth only in http mode
|
||||
if !isTCP {
|
||||
basicAuth = NewBasicAuth()
|
||||
if cfg.GetString("auth-file") != "" {
|
||||
httpAuthorization = true
|
||||
n, err := basicAuth.AddFromFile(cfg.GetString("auth-file"))
|
||||
if err != nil {
|
||||
log.Fatalf("auth-file:%s", err)
|
||||
}
|
||||
log.Printf("auth data added from file %d , total:%d", n, basicAuth.Total())
|
||||
}
|
||||
if len(cfg.GetStringSlice("auth")) > 0 {
|
||||
httpAuthorization = true
|
||||
n := basicAuth.Add(cfg.GetStringSlice("auth"))
|
||||
log.Printf("auth data added %d, total:%d", n, basicAuth.Total())
|
||||
}
|
||||
}
|
||||
|
||||
//listen
|
||||
sc := NewServerChannel(bindIP, bindPort)
|
||||
var err error
|
||||
if localIsTls {
|
||||
err = sc.ListenTls(certBytes, keyBytes, connHandler)
|
||||
} else {
|
||||
err = sc.ListenTCP(connHandler)
|
||||
}
|
||||
//listen fail
|
||||
if err != nil {
|
||||
log.Fatalf("ERR:%s", err)
|
||||
} else {
|
||||
log.Printf("%s %sproxy on %s", protocolStr, localIsTlsStr, (*sc.Listener).Addr())
|
||||
}
|
||||
//block main()
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
cleanupDone := make(chan bool)
|
||||
signal.Notify(signalChan,
|
||||
os.Interrupt,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT)
|
||||
go func() {
|
||||
for _ = range signalChan {
|
||||
if outPool != nil {
|
||||
fmt.Println("\nReceived an interrupt, stopping services...")
|
||||
outPool.ReleaseAll()
|
||||
//time.Sleep(time.Second * 10)
|
||||
// fmt.Println("done")
|
||||
}
|
||||
cleanupDone <- true
|
||||
}
|
||||
}()
|
||||
<-cleanupDone
|
||||
}
|
||||
func connHandler(inConn net.Conn) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.Printf("connHandler crashed,err:%s\nstack:%s", err, string(debug.Stack()))
|
||||
closeConn(&inConn)
|
||||
}
|
||||
}()
|
||||
if isTCP {
|
||||
tcpHandler(&inConn)
|
||||
} else {
|
||||
httpHandler(&inConn)
|
||||
}
|
||||
}
|
||||
|
||||
func tcpHandler(inConn *net.Conn) {
|
||||
var outConn net.Conn
|
||||
var _outConn interface{}
|
||||
_outConn, err = outPool.Get()
|
||||
if err != nil {
|
||||
log.Printf("connect to %s , err:%s", proxyAddr, err)
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
outConn = _outConn.(net.Conn)
|
||||
inAddr := (*inConn).RemoteAddr().String()
|
||||
outAddr := outConn.RemoteAddr().String()
|
||||
IoBind((*inConn), outConn, func(err error) {
|
||||
log.Printf("conn %s - %s released", inAddr, outAddr)
|
||||
closeConn(inConn)
|
||||
closeConn(&outConn)
|
||||
}, func(n int, d bool) {}, 0)
|
||||
log.Printf("conn %s - %s connected", inAddr, outAddr)
|
||||
}
|
||||
func httpHandler(inConn *net.Conn) {
|
||||
var b [4096]byte
|
||||
var n int
|
||||
n, err = (*inConn).Read(b[:])
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Printf("read err:%s", err)
|
||||
}
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
var method, host, address string
|
||||
index := bytes.IndexByte(b[:], '\n')
|
||||
if index == -1 {
|
||||
log.Printf("data err:%s", string(b[:n])[:50])
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Sscanf(string(b[:index]), "%s%s", &method, &host)
|
||||
if method == "" || host == "" {
|
||||
log.Printf("data err:%s", string(b[:n])[:50])
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
isHTTPS := method == "CONNECT"
|
||||
|
||||
//http basic auth,only http
|
||||
if !isHTTPS {
|
||||
if httpAuthorization {
|
||||
//log.Printf("request :%s", string(b[:n]))
|
||||
authorization, err := getHeader("Authorization", b[:n])
|
||||
if err != nil {
|
||||
fmt.Fprint(*inConn, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized")
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
//log.Printf("Authorization:%s", authorization)
|
||||
basic := strings.Fields(authorization)
|
||||
if len(basic) != 2 {
|
||||
log.Printf("authorization data error,ERR:%s", authorization)
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
user, err := base64.StdEncoding.DecodeString(basic[1])
|
||||
if err != nil {
|
||||
log.Printf("authorization data parse error,ERR:%s", err)
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
authOk := basicAuth.Check(string(user))
|
||||
//log.Printf("auth %s,%v", string(user), authOk)
|
||||
if !authOk {
|
||||
fmt.Fprint(*inConn, "HTTP/1.1 401 Unauthorized\r\n\r\nUnauthorized")
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bytes []byte
|
||||
if isHTTPS { //https访问
|
||||
// [dd:dafds:fsd:dasd:2.2.23.3] or 2.2.23.3 or [dd:dafds:fsd:dasd:2.2.23.3]:2323 or 2.2.23.3:1234
|
||||
address = fixHost(host)
|
||||
if hostIsNoPort(host) { //host不带端口, 默认443
|
||||
address = address + ":443"
|
||||
}
|
||||
} else { //http访问
|
||||
hostPortURL, err := url.Parse(host)
|
||||
if err != nil {
|
||||
log.Printf("url.Parse %s ERR:%s", host, err)
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
_host := fixHost(hostPortURL.Host)
|
||||
address = _host
|
||||
if hostIsNoPort(_host) { //host不带端口, 默认80
|
||||
address = _host + ":80"
|
||||
}
|
||||
if _host != hostPortURL.Host {
|
||||
bytes = []byte(strings.Replace(string(b[:n]), hostPortURL.Host, _host, 1))
|
||||
host = strings.Replace(host, hostPortURL.Host, _host, 1)
|
||||
}
|
||||
}
|
||||
//get url , reslut host is the full url
|
||||
host, err = getURL(b[:n], host)
|
||||
// log.Printf("body:%s", string(b[:n]))
|
||||
// log.Printf("%s:%s", method, host)
|
||||
if err != nil {
|
||||
log.Printf("header data err:%s", err)
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
|
||||
useProxy := false
|
||||
if proxyAddr != "" {
|
||||
if cfg.GetBool("always") {
|
||||
useProxy = true
|
||||
} else {
|
||||
if isHTTPS {
|
||||
checker.Add(address, true, method, "", nil)
|
||||
} else {
|
||||
if bytes != nil {
|
||||
checker.Add(address, false, method, host, bytes)
|
||||
} else {
|
||||
checker.Add(address, false, method, host, b[:n])
|
||||
}
|
||||
}
|
||||
useProxy, _, _ = checker.IsBlocked(address)
|
||||
}
|
||||
// var failN, successN uint
|
||||
// useProxy, failN, successN = checker.IsBlocked(address)
|
||||
//log.Printf("use proxy ? %s : %v ,fail:%d, success:%d", address, useProxy, failN, successN)
|
||||
//log.Printf("use proxy ? %s : %v", address, useProxy)
|
||||
}
|
||||
|
||||
var outConn net.Conn
|
||||
var _outConn interface{}
|
||||
if useProxy {
|
||||
_outConn, err = outPool.Get()
|
||||
if err == nil {
|
||||
outConn = _outConn.(net.Conn)
|
||||
}
|
||||
} else {
|
||||
outConn, err = ConnectHost(address, connTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("connect to %s , err:%s", address, err)
|
||||
closeConn(inConn)
|
||||
return
|
||||
}
|
||||
inAddr := (*inConn).RemoteAddr().String()
|
||||
outAddr := outConn.RemoteAddr().String()
|
||||
|
||||
if isHTTPS {
|
||||
if useProxy {
|
||||
outConn.Write(b[:n])
|
||||
} else {
|
||||
fmt.Fprint(*inConn, "HTTP/1.1 200 Connection established\r\n\r\n")
|
||||
}
|
||||
} else {
|
||||
if bytes != nil {
|
||||
outConn.Write(bytes)
|
||||
} else {
|
||||
outConn.Write(b[:n])
|
||||
}
|
||||
}
|
||||
IoBind(*inConn, outConn, func(err error) {
|
||||
log.Printf("conn %s - %s [%s] released", inAddr, outAddr, address)
|
||||
closeConn(inConn)
|
||||
closeConn(&outConn)
|
||||
}, func(n int, d bool) {}, 0)
|
||||
log.Printf("conn %s - %s [%s] connected", inAddr, outAddr, address)
|
||||
}
|
||||
func closeConn(conn *net.Conn) {
|
||||
if *conn != nil {
|
||||
(*conn).SetDeadline(time.Now().Add(time.Millisecond))
|
||||
(*conn).Close()
|
||||
}
|
||||
}
|
||||
func keygen() (err error) {
|
||||
cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("err:%s", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
cmd = exec.Command("sh", "-c", `openssl req -new -key proxy.key -x509 -days 3650 -out proxy.crt -subj /C=CN/ST=BJ/O="Localhost Ltd"/CN=proxy`)
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("err:%s", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
return
|
||||
}
|
||||
315
map.go
Normal file
315
map.go
Normal file
@ -0,0 +1,315 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var SHARD_COUNT = 32
|
||||
|
||||
// A "thread" safe map of type string:Anything.
|
||||
// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards.
|
||||
type ConcurrentMap []*ConcurrentMapShared
|
||||
|
||||
// A "thread" safe string to anything map.
|
||||
type ConcurrentMapShared struct {
|
||||
items map[string]interface{}
|
||||
sync.RWMutex // Read Write mutex, guards access to internal map.
|
||||
}
|
||||
|
||||
// Creates a new concurrent map.
|
||||
func NewConcurrentMap() ConcurrentMap {
|
||||
m := make(ConcurrentMap, SHARD_COUNT)
|
||||
for i := 0; i < SHARD_COUNT; i++ {
|
||||
m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Returns shard under given key
|
||||
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
|
||||
return m[uint(fnv32(key))%uint(SHARD_COUNT)]
|
||||
}
|
||||
|
||||
func (m ConcurrentMap) MSet(data map[string]interface{}) {
|
||||
for key, value := range data {
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
shard.items[key] = value
|
||||
shard.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the given value under the specified key.
|
||||
func (m ConcurrentMap) Set(key string, value interface{}) {
|
||||
// Get map shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
shard.items[key] = value
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
// Callback to return new element to be inserted into the map
|
||||
// It is called while lock is held, therefore it MUST NOT
|
||||
// try to access other keys in same map, as it can lead to deadlock since
|
||||
// Go sync.RWLock is not reentrant
|
||||
type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{}
|
||||
|
||||
// Insert or Update - updates existing element or inserts a new one using UpsertCb
|
||||
func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) {
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
v, ok := shard.items[key]
|
||||
res = cb(ok, v, value)
|
||||
shard.items[key] = res
|
||||
shard.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// Sets the given value under the specified key if no value was associated with it.
|
||||
func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool {
|
||||
// Get map shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
_, ok := shard.items[key]
|
||||
if !ok {
|
||||
shard.items[key] = value
|
||||
}
|
||||
shard.Unlock()
|
||||
return !ok
|
||||
}
|
||||
|
||||
// Retrieves an element from map under given key.
|
||||
func (m ConcurrentMap) Get(key string) (interface{}, bool) {
|
||||
// Get shard
|
||||
shard := m.GetShard(key)
|
||||
shard.RLock()
|
||||
// Get item from shard.
|
||||
val, ok := shard.items[key]
|
||||
shard.RUnlock()
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Returns the number of elements within the map.
|
||||
func (m ConcurrentMap) Count() int {
|
||||
count := 0
|
||||
for i := 0; i < SHARD_COUNT; i++ {
|
||||
shard := m[i]
|
||||
shard.RLock()
|
||||
count += len(shard.items)
|
||||
shard.RUnlock()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Looks up an item under specified key
|
||||
func (m ConcurrentMap) Has(key string) bool {
|
||||
// Get shard
|
||||
shard := m.GetShard(key)
|
||||
shard.RLock()
|
||||
// See if element is within shard.
|
||||
_, ok := shard.items[key]
|
||||
shard.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Removes an element from the map.
|
||||
func (m ConcurrentMap) Remove(key string) {
|
||||
// Try to get shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
delete(shard.items, key)
|
||||
shard.Unlock()
|
||||
}
|
||||
|
||||
// Removes an element from the map and returns it
|
||||
func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) {
|
||||
// Try to get shard.
|
||||
shard := m.GetShard(key)
|
||||
shard.Lock()
|
||||
v, exists = shard.items[key]
|
||||
delete(shard.items, key)
|
||||
shard.Unlock()
|
||||
return v, exists
|
||||
}
|
||||
|
||||
// Checks if map is empty.
|
||||
func (m ConcurrentMap) IsEmpty() bool {
|
||||
return m.Count() == 0
|
||||
}
|
||||
|
||||
// Used by the Iter & IterBuffered functions to wrap two variables together over a channel,
|
||||
type Tuple struct {
|
||||
Key string
|
||||
Val interface{}
|
||||
}
|
||||
|
||||
// Returns an iterator which could be used in a for range loop.
|
||||
//
|
||||
// Deprecated: using IterBuffered() will get a better performence
|
||||
func (m ConcurrentMap) Iter() <-chan Tuple {
|
||||
chans := snapshot(m)
|
||||
ch := make(chan Tuple)
|
||||
go fanIn(chans, ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
// Returns a buffered iterator which could be used in a for range loop.
|
||||
func (m ConcurrentMap) IterBuffered() <-chan Tuple {
|
||||
chans := snapshot(m)
|
||||
total := 0
|
||||
for _, c := range chans {
|
||||
total += cap(c)
|
||||
}
|
||||
ch := make(chan Tuple, total)
|
||||
go fanIn(chans, ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
// Returns a array of channels that contains elements in each shard,
|
||||
// which likely takes a snapshot of `m`.
|
||||
// It returns once the size of each buffered channel is determined,
|
||||
// before all the channels are populated using goroutines.
|
||||
func snapshot(m ConcurrentMap) (chans []chan Tuple) {
|
||||
chans = make([]chan Tuple, SHARD_COUNT)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(SHARD_COUNT)
|
||||
// Foreach shard.
|
||||
for index, shard := range m {
|
||||
go func(index int, shard *ConcurrentMapShared) {
|
||||
// Foreach key, value pair.
|
||||
shard.RLock()
|
||||
chans[index] = make(chan Tuple, len(shard.items))
|
||||
wg.Done()
|
||||
for key, val := range shard.items {
|
||||
chans[index] <- Tuple{key, val}
|
||||
}
|
||||
shard.RUnlock()
|
||||
close(chans[index])
|
||||
}(index, shard)
|
||||
}
|
||||
wg.Wait()
|
||||
return chans
|
||||
}
|
||||
|
||||
// fanIn reads elements from channels `chans` into channel `out`
|
||||
func fanIn(chans []chan Tuple, out chan Tuple) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(chans))
|
||||
for _, ch := range chans {
|
||||
go func(ch chan Tuple) {
|
||||
for t := range ch {
|
||||
out <- t
|
||||
}
|
||||
wg.Done()
|
||||
}(ch)
|
||||
}
|
||||
wg.Wait()
|
||||
close(out)
|
||||
}
|
||||
|
||||
// Returns all items as map[string]interface{}
|
||||
func (m ConcurrentMap) Items() map[string]interface{} {
|
||||
tmp := make(map[string]interface{})
|
||||
|
||||
// Insert items to temporary map.
|
||||
for item := range m.IterBuffered() {
|
||||
tmp[item.Key] = item.Val
|
||||
}
|
||||
|
||||
return tmp
|
||||
}
|
||||
|
||||
// Iterator callback,called for every key,value found in
|
||||
// maps. RLock is held for all calls for a given shard
|
||||
// therefore callback sess consistent view of a shard,
|
||||
// but not across the shards
|
||||
type IterCb func(key string, v interface{})
|
||||
|
||||
// Callback based iterator, cheapest way to read
|
||||
// all elements in a map.
|
||||
func (m ConcurrentMap) IterCb(fn IterCb) {
|
||||
for idx := range m {
|
||||
shard := (m)[idx]
|
||||
shard.RLock()
|
||||
for key, value := range shard.items {
|
||||
fn(key, value)
|
||||
}
|
||||
shard.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Return all keys as []string
|
||||
func (m ConcurrentMap) Keys() []string {
|
||||
count := m.Count()
|
||||
ch := make(chan string, count)
|
||||
go func() {
|
||||
// Foreach shard.
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(SHARD_COUNT)
|
||||
for _, shard := range m {
|
||||
go func(shard *ConcurrentMapShared) {
|
||||
// Foreach key, value pair.
|
||||
shard.RLock()
|
||||
for key := range shard.items {
|
||||
ch <- key
|
||||
}
|
||||
shard.RUnlock()
|
||||
wg.Done()
|
||||
}(shard)
|
||||
}
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Generate keys
|
||||
keys := make([]string, 0, count)
|
||||
for k := range ch {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
//Reviles ConcurrentMap "private" variables to json marshal.
|
||||
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
|
||||
// Create a temporary map, which will hold all item spread across shards.
|
||||
tmp := make(map[string]interface{})
|
||||
|
||||
// Insert items to temporary map.
|
||||
for item := range m.IterBuffered() {
|
||||
tmp[item.Key] = item.Val
|
||||
}
|
||||
return json.Marshal(tmp)
|
||||
}
|
||||
|
||||
func fnv32(key string) uint32 {
|
||||
hash := uint32(2166136261)
|
||||
const prime32 = uint32(16777619)
|
||||
for i := 0; i < len(key); i++ {
|
||||
hash *= prime32
|
||||
hash ^= uint32(key[i])
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// Concurrent map uses Interface{} as its value, therefor JSON Unmarshal
|
||||
// will probably won't know which to type to unmarshal into, in such case
|
||||
// we'll end up with a value of type map[string]interface{}, In most cases this isn't
|
||||
// out value type, this is why we've decided to remove this functionality.
|
||||
|
||||
// func (m *ConcurrentMap) UnmarshalJSON(b []byte) (err error) {
|
||||
// // Reverse process of Marshal.
|
||||
|
||||
// tmp := make(map[string]interface{})
|
||||
|
||||
// // Unmarshal into a single map.
|
||||
// if err := json.Unmarshal(b, &tmp); err != nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // foreach key,value pair in temporary map insert into our concurrent map.
|
||||
// for key, val := range tmp {
|
||||
// m.Set(key, val)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
145
pool.go
Executable file
145
pool.go
Executable file
@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
//ConnPool to use
|
||||
type ConnPool interface {
|
||||
Get() (conn interface{}, err error)
|
||||
Put(conn interface{})
|
||||
ReleaseAll()
|
||||
Len() (length int)
|
||||
}
|
||||
type poolConfig struct {
|
||||
Factory func() (interface{}, error)
|
||||
IsActive func(interface{}) bool
|
||||
Release func(interface{})
|
||||
InitialCap int
|
||||
MaxCap int
|
||||
}
|
||||
|
||||
func NewConnPool(poolConfig poolConfig) (pool ConnPool, err error) {
|
||||
p := netPool{
|
||||
config: poolConfig,
|
||||
conns: make(chan interface{}, poolConfig.MaxCap),
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
//log.Printf("pool MaxCap:%d", poolConfig.MaxCap)
|
||||
if poolConfig.MaxCap > 0 {
|
||||
err = p.initAutoFill(false)
|
||||
if err == nil {
|
||||
p.initAutoFill(true)
|
||||
}
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
type netPool struct {
|
||||
conns chan interface{}
|
||||
lock *sync.Mutex
|
||||
config poolConfig
|
||||
}
|
||||
|
||||
func (p *netPool) initAutoFill(async bool) (err error) {
|
||||
var worker = func() (err error) {
|
||||
for {
|
||||
//log.Printf("pool fill: %v , len: %d", p.Len() <= p.config.InitialCap/2, p.Len())
|
||||
if p.Len() <= p.config.InitialCap/2 {
|
||||
p.lock.Lock()
|
||||
errN := 0
|
||||
for i := 0; i < p.config.InitialCap; i++ {
|
||||
c, err := p.config.Factory()
|
||||
if err != nil {
|
||||
errN++
|
||||
if async {
|
||||
continue
|
||||
} else {
|
||||
p.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
select {
|
||||
case p.conns <- c:
|
||||
default:
|
||||
p.config.Release(c)
|
||||
break
|
||||
}
|
||||
if p.Len() >= p.config.InitialCap {
|
||||
break
|
||||
}
|
||||
}
|
||||
if errN > 0 {
|
||||
log.Printf("fill conn pool fail , ERRN:%d", errN)
|
||||
}
|
||||
p.lock.Unlock()
|
||||
}
|
||||
if !async {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
if async {
|
||||
go worker()
|
||||
} else {
|
||||
err = worker()
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (p *netPool) Get() (conn interface{}, err error) {
|
||||
// defer func() {
|
||||
// log.Printf("pool len : %d", p.Len())
|
||||
// }()
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
// for {
|
||||
select {
|
||||
case conn = <-p.conns:
|
||||
if p.config.IsActive(conn) {
|
||||
return
|
||||
}
|
||||
p.config.Release(conn)
|
||||
default:
|
||||
conn, err = p.config.Factory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
func (p *netPool) Put(conn interface{}) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if !p.config.IsActive(conn) {
|
||||
p.config.Release(conn)
|
||||
}
|
||||
select {
|
||||
case p.conns <- conn:
|
||||
default:
|
||||
p.config.Release(conn)
|
||||
}
|
||||
}
|
||||
func (p *netPool) ReleaseAll() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
close(p.conns)
|
||||
for c := range p.conns {
|
||||
p.config.Release(c)
|
||||
}
|
||||
p.conns = make(chan interface{}, p.config.InitialCap)
|
||||
|
||||
}
|
||||
func (p *netPool) Len() (length int) {
|
||||
return len(p.conns)
|
||||
}
|
||||
72
proxy.toml
Normal file
72
proxy.toml
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
#####################################
|
||||
##############parent#################
|
||||
#####################################
|
||||
#parent proxy address,such as: 223.78.2.33:8090
|
||||
parent=""
|
||||
|
||||
#parent proxy is tls
|
||||
parent-tls=false
|
||||
|
||||
#tcp timeout milliseconds when connect to real server or parent proxy
|
||||
tcp-timeout=2000
|
||||
|
||||
#conn pool size , which connect to parent proxy
|
||||
pool-size=50
|
||||
|
||||
#always use parent proxy
|
||||
always=false
|
||||
|
||||
#####################################
|
||||
##############local##################
|
||||
#####################################
|
||||
|
||||
#local ip to bind
|
||||
ip="0.0.0.0"
|
||||
|
||||
#local port to listen
|
||||
port=33080
|
||||
|
||||
#local proxy is tls
|
||||
local-tls=false
|
||||
|
||||
#####################################
|
||||
################tls##################
|
||||
#####################################
|
||||
|
||||
#cert file for tls
|
||||
cert="proxy.crt"
|
||||
|
||||
#key file for tls
|
||||
key="proxy.key"
|
||||
|
||||
#####################################
|
||||
################protocol#############
|
||||
#####################################
|
||||
|
||||
#proxy on tcp
|
||||
tcp=false
|
||||
|
||||
#####################################
|
||||
################check################
|
||||
#####################################
|
||||
|
||||
#chekc domain blocked , http request timeout milliseconds when connect to host
|
||||
check-timeout=3000
|
||||
|
||||
#check domain if blocked every interval seconds
|
||||
check-interval=10
|
||||
|
||||
#check if proxy is okay every interval seconds
|
||||
#this is very helpful to proxy fix pool status , zero means:no check
|
||||
check-proxy-interval=3
|
||||
|
||||
#blocked domain file , one domain each line
|
||||
#google.com means (*.)*.google.com
|
||||
blocked="blocked"
|
||||
|
||||
#direct domain file , one domain each line
|
||||
#qq.com means (*.)*.qq.com
|
||||
direct="direct"
|
||||
|
||||
|
||||
36
proxyd
Executable file
36
proxyd
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
PID="/var/run/proxy.pid"
|
||||
NAME="proxy"
|
||||
case "$1" in
|
||||
start)
|
||||
if [ -e ${PID} ]; then
|
||||
echo ${NAME} is running, pid=`cat ${PID}`, please stop first
|
||||
exit 1
|
||||
else
|
||||
monexec run -w /etc/proxy -- proxy >/dev/null 2>&1 &
|
||||
echo $!>${PID}
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ -e ${PID} ]; then
|
||||
kill `cat ${PID}`
|
||||
rm ${PID}
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if [ -e ${PID} ]; then
|
||||
echo ${NAME} is running, pid=`cat ${PID}`
|
||||
else
|
||||
echo ${NAME} is NOT running
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status|restart}"
|
||||
esac
|
||||
|
||||
exit 0
|
||||
57
release.sh
Executable file
57
release.sh
Executable file
@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
VER="2.2"
|
||||
RELEASE="release-${VER}"
|
||||
rm -rf ${RELEASE}
|
||||
mkdir ${RELEASE}
|
||||
set CGO_ENABLED=0
|
||||
#linux
|
||||
GOOS=linux GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-linux-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-linux-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=arm GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=arm64 GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=mips go build && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=mips64 go build && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=mips64le go build && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=mipsle go build && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=ppc64 go build && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=ppc64le go build && tar zcfv "${RELEASE}/proxy-linux-ppc64le.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=linux GOARCH=s390x go build && tar zcfv "${RELEASE}/proxy-linux-s390x.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#android
|
||||
GOOS=android GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-android-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=android GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-android-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=android GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-android-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=android GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-android-arm64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#darwin
|
||||
GOOS=darwin GOARCH=386 go build go build && tar zcfv "${RELEASE}/proxy-darwin-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=darwin GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-darwin-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=darwin GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-darwin-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=darwin GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-darwin-arm64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#dragonfly
|
||||
GOOS=dragonfly GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-dragonfly-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#freebsd
|
||||
GOOS=freebsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-freebsd-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=freebsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-freebsd-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=freebsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-freebsd-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#nacl
|
||||
GOOS=nacl GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-nacl-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=nacl GOARCH=amd64p32 go build && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=nacl GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-nacl-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#netbsd
|
||||
GOOS=netbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-netbsd-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=netbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-netbsd-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=netbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-netbsd-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#openbsd
|
||||
GOOS=openbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-openbsd-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=openbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-openbsd-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=openbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-openbsd-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#plan9
|
||||
GOOS=plan9 GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-plan9-386.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=plan9 GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-plan9-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
GOOS=plan9 GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-plan9-arm.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#solaris
|
||||
GOOS=solaris GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-solaris-amd64.tar.gz" proxy proxy.toml proxyd direct blocked
|
||||
#windows
|
||||
GOOS=windows GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe proxy.toml direct blocked
|
||||
GOOS=windows GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-windows-amd64.tar.gz" proxy.exe proxy.toml direct blocked
|
||||
|
||||
rm proxy proxy.exe
|
||||
125
serve-channel.go
Normal file
125
serve-channel.go
Normal file
@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
type ServerChannel struct {
|
||||
ip string
|
||||
port int
|
||||
Listener *net.Listener
|
||||
UDPListener *net.UDPConn
|
||||
errAcceptHandler func(err error)
|
||||
}
|
||||
|
||||
func NewServerChannel(ip string, port int) ServerChannel {
|
||||
return ServerChannel{
|
||||
ip: ip,
|
||||
port: port,
|
||||
errAcceptHandler: func(err error) {
|
||||
fmt.Printf("accept error , ERR:%s", err)
|
||||
},
|
||||
}
|
||||
}
|
||||
func (sc *ServerChannel) SetErrAcceptHandler(fn func(err error)) {
|
||||
sc.errAcceptHandler = fn
|
||||
}
|
||||
func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.Conn)) (err error) {
|
||||
sc.Listener, err = ListenTls(sc.ip, sc.port, certBytes, keyBytes)
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("ListenTls crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var conn net.Conn
|
||||
conn, err = (*sc.Listener).Accept()
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn(conn)
|
||||
}()
|
||||
} else {
|
||||
sc.errAcceptHandler(err)
|
||||
(*sc.Listener).Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", sc.ip, sc.port))
|
||||
if err == nil {
|
||||
sc.Listener = &l
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("ListenTCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var conn net.Conn
|
||||
conn, err = (*sc.Listener).Accept()
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn(conn)
|
||||
}()
|
||||
} else {
|
||||
sc.errAcceptHandler(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *net.UDPAddr)) (err error) {
|
||||
addr := &net.UDPAddr{IP: net.ParseIP(sc.ip), Port: sc.port}
|
||||
l, err := net.ListenUDP("udp", addr)
|
||||
if err == nil {
|
||||
sc.UDPListener = l
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("ListenUDP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var buf = make([]byte, 2048)
|
||||
n, srcAddr, err := (*sc.UDPListener).ReadFromUDP(buf)
|
||||
if err == nil {
|
||||
packet := buf[0:n]
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("udp data handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn(packet, addr, srcAddr)
|
||||
}()
|
||||
} else {
|
||||
sc.errAcceptHandler(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
219
structs.go
Normal file
219
structs.go
Normal file
@ -0,0 +1,219 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Checker struct {
|
||||
data ConcurrentMap
|
||||
blockedMap ConcurrentMap
|
||||
directMap ConcurrentMap
|
||||
interval int64
|
||||
timeout int
|
||||
}
|
||||
type CheckerItem struct {
|
||||
IsHTTPS bool
|
||||
Method string
|
||||
URL string
|
||||
Domain string
|
||||
Host string
|
||||
Data []byte
|
||||
SuccessCount uint
|
||||
FailCount uint
|
||||
}
|
||||
|
||||
//NewChecker args:
|
||||
//timeout : tcp timeout milliseconds ,connect to host
|
||||
//interval: recheck domain interval seconds
|
||||
func NewChecker(timeout int, interval int64, blockedFile, directFile string) Checker {
|
||||
ch := Checker{
|
||||
data: NewConcurrentMap(),
|
||||
interval: interval,
|
||||
timeout: timeout,
|
||||
}
|
||||
ch.blockedMap = ch.loadMap(blockedFile)
|
||||
ch.directMap = ch.loadMap(directFile)
|
||||
if !ch.blockedMap.IsEmpty() {
|
||||
log.Printf("blocked file loaded , domains : %d", ch.blockedMap.Count())
|
||||
}
|
||||
if !ch.directMap.IsEmpty() {
|
||||
log.Printf("direct file loaded , domains : %d", ch.directMap.Count())
|
||||
}
|
||||
ch.start()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) {
|
||||
dataMap = NewConcurrentMap()
|
||||
if PathExists(f) {
|
||||
_contents, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
log.Printf("load file err:%s", err)
|
||||
return
|
||||
}
|
||||
for _, line := range strings.Split(string(_contents), "\n") {
|
||||
line = strings.Trim(line, "\r \t")
|
||||
if line != "" {
|
||||
dataMap.Set(line, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (c *Checker) start() {
|
||||
go func() {
|
||||
for {
|
||||
for _, v := range c.data.Items() {
|
||||
go func(item CheckerItem) {
|
||||
if c.isNeedCheck(item) {
|
||||
//log.Printf("check %s", item.Domain)
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if item.IsHTTPS {
|
||||
conn, err = ConnectHost(item.Host, c.timeout)
|
||||
if err == nil {
|
||||
conn.SetDeadline(time.Now().Add(time.Millisecond))
|
||||
conn.Close()
|
||||
}
|
||||
} else {
|
||||
err = HTTPGet(item.URL, c.timeout)
|
||||
}
|
||||
if err != nil {
|
||||
item.FailCount = item.FailCount + 1
|
||||
} else {
|
||||
item.SuccessCount = item.SuccessCount + 1
|
||||
}
|
||||
c.data.Set(item.Host, item)
|
||||
}
|
||||
}(v.(CheckerItem))
|
||||
}
|
||||
time.Sleep(time.Second * time.Duration(c.interval))
|
||||
}
|
||||
}()
|
||||
}
|
||||
func (c *Checker) isNeedCheck(item CheckerItem) bool {
|
||||
var minCount uint = 5
|
||||
if (item.SuccessCount >= minCount && item.SuccessCount > item.FailCount) ||
|
||||
(item.FailCount >= minCount && item.SuccessCount > item.FailCount) ||
|
||||
c.domainIsInMap(item.Host, false) ||
|
||||
c.domainIsInMap(item.Host, true) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (c *Checker) IsBlocked(address string) (blocked bool, failN, successN uint) {
|
||||
if c.domainIsInMap(address, true) {
|
||||
return true, 0, 0
|
||||
}
|
||||
if c.domainIsInMap(address, false) {
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
_item, ok := c.data.Get(address)
|
||||
if !ok {
|
||||
return true, 0, 0
|
||||
}
|
||||
item := _item.(CheckerItem)
|
||||
|
||||
return item.FailCount >= item.SuccessCount, item.FailCount, item.SuccessCount
|
||||
}
|
||||
func (c *Checker) domainIsInMap(address string, blockedMap bool) bool {
|
||||
u, err := url.Parse("http://" + address)
|
||||
if err != nil {
|
||||
log.Printf("blocked check , url parse err:%s", err)
|
||||
return true
|
||||
}
|
||||
domainSlice := strings.Split(u.Hostname(), ".")
|
||||
if len(domainSlice) > 1 {
|
||||
subSlice := domainSlice[:len(domainSlice)-1]
|
||||
topDomain := strings.Join(domainSlice[len(domainSlice)-1:], ".")
|
||||
checkDomain := topDomain
|
||||
for i := len(subSlice) - 1; i >= 0; i-- {
|
||||
checkDomain = subSlice[i] + "." + checkDomain
|
||||
if !blockedMap && c.directMap.Has(checkDomain) {
|
||||
return true
|
||||
}
|
||||
if blockedMap && c.blockedMap.Has(checkDomain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (c *Checker) Add(address string, isHTTPS bool, method, URL string, data []byte) {
|
||||
if c.domainIsInMap(address, false) || c.domainIsInMap(address, true) {
|
||||
return
|
||||
}
|
||||
if !isHTTPS && strings.ToLower(method) != "get" {
|
||||
return
|
||||
}
|
||||
var item CheckerItem
|
||||
u := strings.Split(address, ":")
|
||||
item = CheckerItem{
|
||||
URL: URL,
|
||||
Domain: u[0],
|
||||
Host: address,
|
||||
Data: data,
|
||||
IsHTTPS: isHTTPS,
|
||||
Method: method,
|
||||
}
|
||||
c.data.SetIfAbsent(item.Host, item)
|
||||
}
|
||||
|
||||
type BasicAuth struct {
|
||||
data ConcurrentMap
|
||||
}
|
||||
|
||||
func NewBasicAuth() BasicAuth {
|
||||
return BasicAuth{
|
||||
data: NewConcurrentMap(),
|
||||
}
|
||||
}
|
||||
func (ba *BasicAuth) AddFromFile(file string) (n int, err error) {
|
||||
_content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userpassArr := strings.Split(strings.Replace(string(_content), "\r", "", -1), "\n")
|
||||
for _, userpass := range userpassArr {
|
||||
if strings.HasPrefix("#", userpass) {
|
||||
continue
|
||||
}
|
||||
u := strings.Split(strings.Trim(userpass, " "), ":")
|
||||
if len(u) == 2 {
|
||||
ba.data.Set(u[0], u[1])
|
||||
n++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ba *BasicAuth) Add(userpassArr []string) (n int) {
|
||||
for _, userpass := range userpassArr {
|
||||
u := strings.Split(userpass, ":")
|
||||
if len(u) == 2 {
|
||||
ba.data.Set(u[0], u[1])
|
||||
n++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ba *BasicAuth) Check(userpass string) (ok bool) {
|
||||
u := strings.Split(strings.Trim(userpass, " "), ":")
|
||||
if len(u) == 2 {
|
||||
if p, _ok := ba.data.Get(u[0]); _ok {
|
||||
return p.(string) == u[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (ba *BasicAuth) Total() (n int) {
|
||||
n = ba.data.Count()
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user