Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>

This commit is contained in:
arraykeys@gmail.com
2017-09-19 17:16:16 +08:00
parent 6bd396a70b
commit 763652cb01
17 changed files with 7233 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
proxy
*.exe
*.exe~
release-*
proxy.crt
proxy.key

23
CHANGELOG Normal file
View 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.

3
auth-file Normal file
View File

@ -0,0 +1,3 @@
#comment
user3:pass3
user4:pass4

4833
blocked Normal file

File diff suppressed because it is too large Load Diff

115
config.go Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}