mirror of
https://github.com/ehang-io/nps.git
synced 2025-07-02 04:00:42 +00:00
版本重构,加web管理方式
This commit is contained in:
parent
974f605ff6
commit
abc30a9ad1
6
conf/app.conf
Executable file
6
conf/app.conf
Executable file
@ -0,0 +1,6 @@
|
||||
appname = httpMonitor
|
||||
httpport = 8080
|
||||
runmode = dev
|
||||
password=123
|
||||
hostPort=8028
|
||||
|
3
conf/hosts.csv
Normal file
3
conf/hosts.csv
Normal file
@ -0,0 +1,3 @@
|
||||
a.proxy.com,127.0.0.1:81,o2430bnq22jgnmcl
|
||||
b.proxy.com,127.0.0.1:82,o2430bnq22jgnmcl
|
||||
a.o.com,10.1.50.203:80,o2430bnq22jgnmcl
|
|
8
conf/tasks.csv
Normal file
8
conf/tasks.csv
Normal file
@ -0,0 +1,8 @@
|
||||
8021,tunnelServer,10.1.50.203:80,um80bbyvcaiw7n17,,,,1
|
||||
8001,tunnelServer,10.1.50.101:22,h8vcpciko9lczxmb,,,gzip,0
|
||||
8022,tunnelServer,10.1.50.196:4000,vcerzo01q16cdn9n,,,snappy,1
|
||||
8002,udpServer,10.1.50.102:53,zcbsx5kp53cg5yqv,,,,0
|
||||
8003,sock5Server,,9mq856pqq3jq05qv,,,snappy,1
|
||||
8010,sock5Server,,0aj2xfec0mbkann5,test,test,gzip,0
|
||||
8004,httpProxyServer,,y72zj8t074lslln0,,,gzip,0
|
||||
0,hostServer,,o2430bnq22jgnmcl,,,,1
|
|
117
controllers/base.go
Executable file
117
controllers/base.go
Executable file
@ -0,0 +1,117 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/cnlh/easyProxy/lib"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BaseController struct {
|
||||
beego.Controller
|
||||
controllerName string
|
||||
actionName string
|
||||
}
|
||||
|
||||
//初始化参数
|
||||
func (s *BaseController) Prepare() {
|
||||
controllerName, actionName := s.GetControllerAndAction()
|
||||
s.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])
|
||||
s.actionName = strings.ToLower(actionName)
|
||||
if s.GetSession("auth") != true {
|
||||
s.Redirect("/login/index", 302)
|
||||
}
|
||||
}
|
||||
|
||||
//加载模板
|
||||
func (s *BaseController) display(tpl ...string) {
|
||||
var tplname string
|
||||
if len(tpl) > 0 {
|
||||
tplname = strings.Join([]string{tpl[0], "html"}, ".")
|
||||
} else {
|
||||
tplname = s.controllerName + "/" + s.actionName + ".html"
|
||||
}
|
||||
s.Data["menu"] = s.actionName
|
||||
ip := s.Ctx.Request.Host
|
||||
s.Data["ip"] = lib.Gethostbyname(ip[0:strings.LastIndex(ip, ":")])
|
||||
s.Data["p"] = *lib.TcpPort
|
||||
s.Data["proxyPort"] = beego.AppConfig.String("hostPort")
|
||||
s.Layout = "public/layout.html"
|
||||
s.TplName = tplname
|
||||
}
|
||||
|
||||
//错误
|
||||
func (s *BaseController) error() {
|
||||
s.Layout = "public/layout.html"
|
||||
s.TplName = "public/error.html"
|
||||
}
|
||||
|
||||
//去掉没有err返回值的int
|
||||
func (s *BaseController) GetIntNoErr(key string, def ...int) int {
|
||||
strv := s.Ctx.Input.Query(key)
|
||||
if len(strv) == 0 && len(def) > 0 {
|
||||
return def[0]
|
||||
}
|
||||
val, _ := strconv.Atoi(strv)
|
||||
return val
|
||||
}
|
||||
|
||||
//获取去掉错误的bool值
|
||||
func (s *BaseController) GetBoolNoErr(key string, def ...bool) bool {
|
||||
strv := s.Ctx.Input.Query(key)
|
||||
if len(strv) == 0 && len(def) > 0 {
|
||||
return def[0]
|
||||
}
|
||||
val, _ := strconv.ParseBool(strv)
|
||||
return val
|
||||
}
|
||||
|
||||
//ajax正确返回
|
||||
func (s *BaseController) AjaxOk(str string) {
|
||||
s.Data["json"] = ajax(str, 1)
|
||||
s.ServeJSON()
|
||||
s.StopRun()
|
||||
}
|
||||
|
||||
//ajax错误返回
|
||||
func (s *BaseController) AjaxErr(str string) {
|
||||
s.Data["json"] = ajax(str, 0)
|
||||
s.ServeJSON()
|
||||
s.StopRun()
|
||||
}
|
||||
|
||||
//组装ajax
|
||||
func ajax(str string, status int) (map[string]interface{}) {
|
||||
json := make(map[string]interface{})
|
||||
json["status"] = status
|
||||
json["msg"] = str
|
||||
return json
|
||||
}
|
||||
|
||||
//ajax table返回
|
||||
func (s *BaseController) AjaxTable(list interface{}, cnt int, recordsTotal int) {
|
||||
json := make(map[string]interface{})
|
||||
json["data"] = list
|
||||
json["draw"] = s.GetIntNoErr("draw")
|
||||
json["err"] = ""
|
||||
json["recordsTotal"] = recordsTotal
|
||||
json["recordsFiltered"] = cnt
|
||||
s.Data["json"] = json
|
||||
s.ServeJSON()
|
||||
s.StopRun()
|
||||
}
|
||||
|
||||
//ajax table参数
|
||||
func (s *BaseController) GetAjaxParams() (start, limit int) {
|
||||
s.Ctx.Input.Bind(&start, "start")
|
||||
s.Ctx.Input.Bind(&limit, "length")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BaseController) SetInfo(name string) {
|
||||
s.Data["name"] = name
|
||||
}
|
||||
|
||||
func (s *BaseController) SetType(name string) {
|
||||
s.Data["type"] = name
|
||||
}
|
165
controllers/index.go
Executable file
165
controllers/index.go
Executable file
@ -0,0 +1,165 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/cnlh/easyProxy/lib"
|
||||
)
|
||||
|
||||
type IndexController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (s *IndexController) Index() {
|
||||
s.SetInfo("使用说明")
|
||||
s.display("index/index")
|
||||
}
|
||||
|
||||
func (s *IndexController) Tcp() {
|
||||
s.SetInfo("tcp隧道管理")
|
||||
s.SetType("tunnelServer")
|
||||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) Udp() {
|
||||
s.SetInfo("udp隧道管理")
|
||||
s.SetType("udpServer")
|
||||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) Socks5() {
|
||||
s.SetInfo("socks5管理")
|
||||
s.SetType("sock5Server")
|
||||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) Http() {
|
||||
s.SetInfo("http代理管理")
|
||||
s.SetType("httpProxyServer")
|
||||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) Host() {
|
||||
s.SetInfo("host模式管理")
|
||||
s.SetType("hostServer")
|
||||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) GetTaskList() {
|
||||
start, length := s.GetAjaxParams()
|
||||
taskType := s.GetString("type")
|
||||
list, cnt := lib.CsvDb.GetTaskList(start, length, taskType)
|
||||
s.AjaxTable(list, cnt, cnt)
|
||||
}
|
||||
|
||||
func (s *IndexController) Add() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["type"] = s.GetString("type")
|
||||
s.SetInfo("新增")
|
||||
s.display()
|
||||
} else {
|
||||
t := &lib.TaskList{
|
||||
TcpPort: s.GetIntNoErr("port"),
|
||||
Mode: s.GetString("type"),
|
||||
Target: s.GetString("target"),
|
||||
VerifyKey: lib.GetRandomString(16),
|
||||
U: s.GetString("u"),
|
||||
P: s.GetString("p"),
|
||||
Compress: s.GetString("compress"),
|
||||
IsRun: 0,
|
||||
}
|
||||
lib.CsvDb.NewTask(t)
|
||||
if err := lib.AddTask(t); err != nil {
|
||||
s.AjaxErr(err.Error())
|
||||
} else {
|
||||
s.AjaxOk("添加成功")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IndexController) Edit() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
vKey := s.GetString("vKey")
|
||||
if t, err := lib.CsvDb.GetTask(vKey); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
s.Data["t"] = t
|
||||
}
|
||||
s.SetInfo("修改")
|
||||
s.display()
|
||||
} else {
|
||||
vKey := s.GetString("vKey")
|
||||
if t, err := lib.CsvDb.GetTask(vKey); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
t.TcpPort = s.GetIntNoErr("port")
|
||||
t.Mode = s.GetString("type")
|
||||
t.Target = s.GetString("target")
|
||||
t.U = s.GetString("u")
|
||||
t.P = s.GetString("p")
|
||||
t.Compress = s.GetString("compress")
|
||||
lib.CsvDb.UpdateTask(t)
|
||||
lib.StopServer(t.VerifyKey)
|
||||
lib.StartTask(t.VerifyKey)
|
||||
}
|
||||
s.AjaxOk("修改成功")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IndexController) Stop() {
|
||||
vKey := s.GetString("vKey")
|
||||
if err := lib.StopServer(vKey); err != nil {
|
||||
s.AjaxErr("停止失败")
|
||||
}
|
||||
s.AjaxOk("停止成功")
|
||||
}
|
||||
func (s *IndexController) Del() {
|
||||
vKey := s.GetString("vKey")
|
||||
if err := lib.DelTask(vKey); err != nil {
|
||||
s.AjaxErr("删除失败")
|
||||
}
|
||||
s.AjaxOk("删除成功")
|
||||
}
|
||||
|
||||
func (s *IndexController) Start() {
|
||||
vKey := s.GetString("vKey")
|
||||
if err := lib.StartTask(vKey); err != nil {
|
||||
s.AjaxErr("开启失败")
|
||||
}
|
||||
s.AjaxOk("开启成功")
|
||||
}
|
||||
|
||||
func (s *IndexController) HostList() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["vkey"] = s.GetString("vkey")
|
||||
s.SetInfo("域名列表")
|
||||
s.display("index/hlist")
|
||||
} else {
|
||||
start, length := s.GetAjaxParams()
|
||||
vkey := s.GetString("vkey")
|
||||
list, cnt := lib.CsvDb.GetHostList(start, length, vkey)
|
||||
s.AjaxTable(list, cnt, cnt)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IndexController) DelHost() {
|
||||
host := s.GetString("host")
|
||||
if err := lib.CsvDb.DelHost(host); err != nil {
|
||||
s.AjaxErr("删除失败")
|
||||
}
|
||||
s.AjaxOk("删除成功")
|
||||
}
|
||||
|
||||
func (s *IndexController) AddHost() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["vkey"] = s.GetString("vkey")
|
||||
s.SetInfo("新增")
|
||||
s.display("index/hadd")
|
||||
} else {
|
||||
h := &lib.HostList{
|
||||
Vkey: s.GetString("vkey"),
|
||||
Host: s.GetString("host"),
|
||||
Target: s.GetString("target"),
|
||||
}
|
||||
lib.CsvDb.NewHost(h)
|
||||
s.AjaxOk("添加成功")
|
||||
}
|
||||
}
|
27
controllers/login.go
Executable file
27
controllers/login.go
Executable file
@ -0,0 +1,27 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
type LoginController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (self *LoginController) Index() {
|
||||
self.TplName = "login/index.html"
|
||||
}
|
||||
func (self *LoginController) Verify() {
|
||||
if self.GetString("psd") == beego.AppConfig.String("password") {
|
||||
self.SetSession("auth", true)
|
||||
self.Data["json"] = map[string]interface{}{"status": 1, "msg": "验证成功"}
|
||||
self.ServeJSON()
|
||||
} else {
|
||||
self.Data["json"] = map[string]interface{}{"status": 0, "msg": "验证失败"}
|
||||
self.ServeJSON()
|
||||
}
|
||||
}
|
||||
func (self *LoginController) Out() {
|
||||
self.SetSession("auth", false)
|
||||
self.Redirect("/login/index", 302)
|
||||
}
|
121
http.go
121
http.go
@ -1,121 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
http.ReadRequest()
|
||||
http.ReadResponse()
|
||||
httputil.DumpRequest()
|
||||
httputil.DumpResponse()
|
||||
*/
|
||||
|
||||
// 将request 的处理
|
||||
func EncodeRequest(r *http.Request) ([]byte, error) {
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
// 写签名
|
||||
binary.Write(raw, binary.LittleEndian, []byte("sign"))
|
||||
reqBytes, err := httputil.DumpRequest(r, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 写body数据长度 + 1
|
||||
binary.Write(raw, binary.LittleEndian, int32(len(reqBytes)+1))
|
||||
// 判断是否为http或者https的标识1字节
|
||||
binary.Write(raw, binary.LittleEndian, bool(r.URL.Scheme == "https"))
|
||||
if err := binary.Write(raw, binary.LittleEndian, reqBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return raw.Bytes(), nil
|
||||
}
|
||||
|
||||
// 将字节转为request
|
||||
func DecodeRequest(data []byte) (*http.Request, error) {
|
||||
if len(data) <= 100 {
|
||||
return nil, errors.New("待解码的字节长度太小")
|
||||
}
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(data[1:])))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
str := strings.Split(req.Host, ":")
|
||||
req.Host, err = getHost(str[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scheme := "http"
|
||||
if data[0] == 1 {
|
||||
scheme = "https"
|
||||
}
|
||||
req.URL, _ = url.Parse(fmt.Sprintf("%s://%s%s", scheme, req.Host, req.RequestURI))
|
||||
req.RequestURI = ""
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
//// 将response转为字节
|
||||
func EncodeResponse(r *http.Response) ([]byte, error) {
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
binary.Write(raw, binary.LittleEndian, []byte("sign"))
|
||||
respBytes, err := httputil.DumpResponse(r, true)
|
||||
if config.Replace == 1 {
|
||||
respBytes = replaceHost(respBytes)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
zw := gzip.NewWriter(&buf)
|
||||
zw.Write(respBytes)
|
||||
zw.Close()
|
||||
binary.Write(raw, binary.LittleEndian, int32(len(buf.Bytes())))
|
||||
if err := binary.Write(raw, binary.LittleEndian, buf.Bytes()); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
return raw.Bytes(), nil
|
||||
}
|
||||
|
||||
//// 将字节转为response
|
||||
func DecodeResponse(data []byte) (*http.Response, error) {
|
||||
zr, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zr.Close()
|
||||
resp, err := http.ReadResponse(bufio.NewReader(zr), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func getHost(str string) (string, error) {
|
||||
for _, v := range config.SiteList {
|
||||
if v.Host == str {
|
||||
return v.Url + ":" + strconv.Itoa(v.Port), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("没有找到解析的的host!")
|
||||
}
|
||||
|
||||
func replaceHost(resp []byte) []byte {
|
||||
str := string(resp)
|
||||
for _, v := range config.SiteList {
|
||||
str = strings.Replace(str, v.Url+":"+strconv.Itoa(v.Port), v.Host, -1)
|
||||
str = strings.Replace(str, v.Url, v.Host, -1)
|
||||
}
|
||||
return []byte(str)
|
||||
}
|
51
json.go
51
json.go
@ -1,51 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
//定义配置文件解析后的结构
|
||||
type Server struct {
|
||||
Ip string
|
||||
Port int
|
||||
Tcp int
|
||||
Vkey string
|
||||
Num int
|
||||
}
|
||||
|
||||
type Site struct {
|
||||
Host string
|
||||
Url string
|
||||
Port int
|
||||
}
|
||||
type Config struct {
|
||||
Server Server
|
||||
SiteList []Site
|
||||
Replace int
|
||||
}
|
||||
type JsonStruct struct {
|
||||
}
|
||||
|
||||
func NewJsonStruct() *JsonStruct {
|
||||
return &JsonStruct{}
|
||||
}
|
||||
func (jst *JsonStruct) Load(filename string) (Config, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
config := Config{}
|
||||
if err != nil {
|
||||
return config, errors.New("配置文件打开错误")
|
||||
}
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return config, errors.New("配置文件解析错误")
|
||||
}
|
||||
if config.Server.Tcp <= 0 || config.Server.Tcp >= 65536 {
|
||||
return config, errors.New("请输入正确的tcp端口")
|
||||
}
|
||||
if config.Server.Vkey == "" {
|
||||
return config, errors.New("密钥不能为空!")
|
||||
}
|
||||
return config, nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -13,12 +13,14 @@ type TRPClient struct {
|
||||
svrAddr string
|
||||
tcpNum int
|
||||
sync.Mutex
|
||||
vKey string
|
||||
}
|
||||
|
||||
func NewRPClient(svraddr string, tcpNum int) *TRPClient {
|
||||
func NewRPClient(svraddr string, tcpNum int, vKey string) *TRPClient {
|
||||
c := new(TRPClient)
|
||||
c.svrAddr = svraddr
|
||||
c.tcpNum = tcpNum
|
||||
c.vKey = vKey
|
||||
return c
|
||||
}
|
||||
|
||||
@ -26,9 +28,6 @@ func (s *TRPClient) Start() error {
|
||||
for i := 0; i < s.tcpNum; i++ {
|
||||
go s.newConn()
|
||||
}
|
||||
for {
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -49,7 +48,7 @@ func (s *TRPClient) newConn() error {
|
||||
|
||||
func (s *TRPClient) process(c *Conn) error {
|
||||
c.SetAlive()
|
||||
if _, err := c.Write(getverifyval()); err != nil {
|
||||
if _, err := c.Write([]byte(getverifyval(s.vKey))); err != nil {
|
||||
return err
|
||||
}
|
||||
c.wMain()
|
||||
@ -63,20 +62,20 @@ func (s *TRPClient) process(c *Conn) error {
|
||||
}
|
||||
switch flags {
|
||||
case VERIFY_EER:
|
||||
log.Fatal("vkey不正确,请检查配置文件")
|
||||
log.Fatalln("vkey:", s.vKey, "不正确,服务端拒绝连接,请检查")
|
||||
case RES_SIGN: //代理请求模式
|
||||
if err := s.dealHttp(c); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
case WORK_CHAN: //隧道模式,每次开启10个,加快连接速度
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := 0; i < 10; i++ {
|
||||
go s.dealChan()
|
||||
}
|
||||
case RES_MSG:
|
||||
log.Println("服务端返回错误。")
|
||||
default:
|
||||
log.Println("无法解析该错误。")
|
||||
log.Println("无法解析该错误。", flags)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -90,7 +89,7 @@ func (s *TRPClient) dealChan() error {
|
||||
return err
|
||||
}
|
||||
//验证
|
||||
if _, err := conn.Write(getverifyval()); err != nil {
|
||||
if _, err := conn.Write([]byte(getverifyval(s.vKey))); err != nil {
|
||||
return err
|
||||
}
|
||||
//默认长连接保持
|
||||
@ -99,7 +98,7 @@ func (s *TRPClient) dealChan() error {
|
||||
//写标志
|
||||
c.wChan()
|
||||
//获取连接的host type(tcp or udp)
|
||||
typeStr, host, err := c.GetHostFromConn()
|
||||
typeStr, host, en, de, err := c.GetHostFromConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -109,15 +108,16 @@ func (s *TRPClient) dealChan() error {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
go relay(NewConn(server), c, DataDecode)
|
||||
relay(c, NewConn(server), DataEncode)
|
||||
go relay(NewConn(server), c, de)
|
||||
relay(c, NewConn(server), en)
|
||||
return nil
|
||||
}
|
||||
|
||||
//http模式处理
|
||||
func (s *TRPClient) dealHttp(c *Conn) error {
|
||||
buf := make([]byte, 1024*32)
|
||||
n, err := c.ReadFromCompress(buf, DataDecode)
|
||||
en, de := c.GetCompressTypeFromConn()
|
||||
n, err := c.ReadFromCompress(buf, de)
|
||||
if err != nil {
|
||||
c.wError()
|
||||
return err
|
||||
@ -133,7 +133,7 @@ func (s *TRPClient) dealHttp(c *Conn) error {
|
||||
return err
|
||||
}
|
||||
c.wSign()
|
||||
n, err = c.WriteCompress(respBytes, DataEncode)
|
||||
n, err = c.WriteCompress(respBytes, en)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
146
conn.go → lib/conn.go
Normal file → Executable file
146
conn.go → lib/conn.go
Normal file → Executable file
@ -1,6 +1,7 @@
|
||||
package main
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/binary"
|
||||
@ -10,12 +11,63 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SnappyConn struct {
|
||||
w *snappy.Writer
|
||||
r *snappy.Reader
|
||||
}
|
||||
|
||||
func NewSnappyConn(conn net.Conn) *SnappyConn {
|
||||
c := new(SnappyConn)
|
||||
c.w = snappy.NewBufferedWriter(conn)
|
||||
c.r = snappy.NewReader(conn)
|
||||
return c
|
||||
}
|
||||
|
||||
func (s *SnappyConn) Write(b []byte) (n int, err error) {
|
||||
if n, err = s.w.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
err = s.w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SnappyConn) Read(b []byte) (n int, err error) {
|
||||
return s.r.Read(b)
|
||||
}
|
||||
|
||||
type GzipConn struct {
|
||||
w *gzip.Writer
|
||||
r *gzip.Reader
|
||||
}
|
||||
|
||||
func NewGzipConn(conn net.Conn) *GzipConn {
|
||||
c := new(GzipConn)
|
||||
c.w = gzip.NewWriter(conn)
|
||||
c.r, err = gzip.NewReader(conn)
|
||||
return c
|
||||
}
|
||||
|
||||
func (s *GzipConn) Write(b []byte) (n int, err error) {
|
||||
if n, err = s.w.Write(b); err != nil || err == io.EOF {
|
||||
err = s.w.Flush()
|
||||
s.w.Close()
|
||||
return
|
||||
}
|
||||
err = s.w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *GzipConn) Read(b []byte) (n int, err error) {
|
||||
return s.r.Read(b)
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
conn net.Conn
|
||||
}
|
||||
@ -32,7 +84,7 @@ func (s *Conn) ReadLen(len int) ([]byte, error) {
|
||||
buff := make([]byte, 1024)
|
||||
c := 0
|
||||
for {
|
||||
clen, err := s.conn.Read(buff)
|
||||
clen, err := s.Read(buff)
|
||||
if err != nil && err != io.EOF {
|
||||
return raw, err
|
||||
}
|
||||
@ -64,7 +116,6 @@ func (s *Conn) GetLen() (int, error) {
|
||||
//写入长度
|
||||
func (s *Conn) WriteLen(buf []byte) (int, error) {
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
|
||||
if err := binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
@ -79,23 +130,22 @@ func (s *Conn) WriteLen(buf []byte) (int, error) {
|
||||
//读取flag
|
||||
func (s *Conn) ReadFlag() (string, error) {
|
||||
val := make([]byte, 4)
|
||||
_, err := s.conn.Read(val)
|
||||
_, err := s.Read(val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(val), err
|
||||
}
|
||||
|
||||
//读取host
|
||||
func (s *Conn) GetHostFromConn() (typeStr string, host string, err error) {
|
||||
//读取host 连接地址 压缩类型
|
||||
func (s *Conn) GetHostFromConn() (typeStr string, host string, en, de int, err error) {
|
||||
retry:
|
||||
ltype := make([]byte, 3)
|
||||
_, err = s.Read(ltype)
|
||||
if err != nil {
|
||||
if _, err = s.Read(ltype); err != nil {
|
||||
return
|
||||
}
|
||||
typeStr = string(ltype)
|
||||
if typeStr == TEST_FLAG {
|
||||
if typeStr = string(ltype); typeStr == TEST_FLAG {
|
||||
en, de = s.GetCompressTypeFromConn()
|
||||
goto retry
|
||||
}
|
||||
len, err := s.GetLen()
|
||||
@ -103,15 +153,14 @@ retry:
|
||||
return
|
||||
}
|
||||
hostByte := make([]byte, len)
|
||||
_, err = s.conn.Read(hostByte)
|
||||
if err != nil {
|
||||
if _, err = s.Read(hostByte); err != nil {
|
||||
return
|
||||
}
|
||||
host = string(hostByte)
|
||||
return
|
||||
}
|
||||
|
||||
//写tcp host
|
||||
//写连接类型 和 host地址
|
||||
func (s *Conn) WriteHost(ltype string, host string) (int, error) {
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
binary.Write(raw, binary.LittleEndian, []byte(ltype))
|
||||
@ -130,45 +179,34 @@ func (s *Conn) SetAlive() {
|
||||
|
||||
//从tcp报文中解析出host
|
||||
func (s *Conn) GetHost() (method, address string, rb []byte, err error) {
|
||||
var b [2048]byte
|
||||
var b [32 * 1024]byte
|
||||
var n int
|
||||
var host string
|
||||
if n, err = s.Read(b[:]); err != nil {
|
||||
return
|
||||
}
|
||||
rb = b[:n]
|
||||
//TODO:某些不规范报文可能会有问题
|
||||
fmt.Sscanf(string(b[:n]), "%s", &method)
|
||||
reg, err := regexp.Compile(`(\w+:\/\/)([^/:]+)(:\d*)?`)
|
||||
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(rb)))
|
||||
if err != nil {
|
||||
log.Println("解析host出错:", err)
|
||||
return
|
||||
}
|
||||
host = string(reg.Find(b[:]))
|
||||
hostPortURL, err := url.Parse(host)
|
||||
hostPortURL, err := url.Parse(r.Host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hostPortURL.Opaque == "443" { //https访问
|
||||
address = hostPortURL.Scheme + ":443"
|
||||
address = r.Host + ":443"
|
||||
} else { //http访问
|
||||
if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80
|
||||
address = hostPortURL.Host + ":80"
|
||||
address = r.Host + ":80"
|
||||
} else {
|
||||
address = hostPortURL.Host
|
||||
address = r.Host
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Conn) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
||||
func (s *Conn) Write(b []byte) (int, error) {
|
||||
return s.conn.Write(b)
|
||||
}
|
||||
func (s *Conn) Read(b []byte) (int, error) {
|
||||
return s.conn.Read(b)
|
||||
}
|
||||
//压缩方式读
|
||||
func (s *Conn) ReadFromCompress(b []byte, compress int) (int, error) {
|
||||
switch compress {
|
||||
case COMPRESS_GZIP_DECODE:
|
||||
@ -186,6 +224,7 @@ func (s *Conn) ReadFromCompress(b []byte, compress int) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
//压缩方式写
|
||||
func (s *Conn) WriteCompress(b []byte, compress int) (n int, err error) {
|
||||
switch compress {
|
||||
case COMPRESS_GZIP_ENCODE:
|
||||
@ -193,32 +232,61 @@ func (s *Conn) WriteCompress(b []byte, compress int) (n int, err error) {
|
||||
if n, err = w.Write(b); err == nil {
|
||||
w.Flush()
|
||||
}
|
||||
err = w.Close()
|
||||
case COMPRESS_SNAPY_ENCODE:
|
||||
w := snappy.NewBufferedWriter(s)
|
||||
if n, err = w.Write(b); err == nil {
|
||||
w.Flush()
|
||||
}
|
||||
err = w.Close()
|
||||
case COMPRESS_NONE:
|
||||
n, err = s.Write(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Conn) wError() (int, error) {
|
||||
return s.conn.Write([]byte(RES_MSG))
|
||||
//写压缩方式
|
||||
func (s *Conn) WriteCompressType(en, de int) {
|
||||
s.Write([]byte(strconv.Itoa(en) + strconv.Itoa(de)))
|
||||
}
|
||||
|
||||
//获取压缩方式
|
||||
func (s *Conn) GetCompressTypeFromConn() (en, de int) {
|
||||
buf := make([]byte, 2)
|
||||
s.Read(buf)
|
||||
en, _ = strconv.Atoi(string(buf[0]))
|
||||
de, _ = strconv.Atoi(string(buf[1]))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Conn) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
||||
func (s *Conn) Write(b []byte) (int, error) {
|
||||
return s.conn.Write(b)
|
||||
}
|
||||
|
||||
func (s *Conn) Read(b []byte) (int, error) {
|
||||
return s.conn.Read(b)
|
||||
}
|
||||
|
||||
func (s *Conn) wError() (int, error) {
|
||||
return s.Write([]byte(RES_MSG))
|
||||
}
|
||||
|
||||
func (s *Conn) wSign() (int, error) {
|
||||
return s.conn.Write([]byte(RES_SIGN))
|
||||
return s.Write([]byte(RES_SIGN))
|
||||
}
|
||||
|
||||
func (s *Conn) wMain() (int, error) {
|
||||
return s.conn.Write([]byte(WORK_MAIN))
|
||||
return s.Write([]byte(WORK_MAIN))
|
||||
}
|
||||
|
||||
func (s *Conn) wChan() (int, error) {
|
||||
return s.conn.Write([]byte(WORK_CHAN))
|
||||
return s.Write([]byte(WORK_CHAN))
|
||||
}
|
||||
|
||||
func (s *Conn) wTest() (int, error) {
|
||||
return s.conn.Write([]byte(TEST_FLAG))
|
||||
return s.Write([]byte(TEST_FLAG))
|
||||
}
|
285
lib/csv.go
Normal file
285
lib/csv.go
Normal file
@ -0,0 +1,285 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type TaskList struct {
|
||||
TcpPort int //服务端与客户端通信端口
|
||||
Mode string //启动方式
|
||||
Target string //目标
|
||||
VerifyKey string //flag
|
||||
U string //socks5验证用户名
|
||||
P string //socks5验证密码
|
||||
Compress string //压缩方式
|
||||
Start int //是否开启
|
||||
IsRun int //是否在运行
|
||||
ClientStatus int //客户端状态
|
||||
}
|
||||
|
||||
type HostList struct {
|
||||
Vkey string //服务端与客户端通信端口
|
||||
Host string //启动方式
|
||||
Target string //目标
|
||||
}
|
||||
|
||||
func NewCsv(path string, bridge *Tunnel, runList map[string]interface{}) *Csv {
|
||||
c := new(Csv)
|
||||
c.Path = path
|
||||
c.Bridge = bridge
|
||||
c.RunList = runList
|
||||
return c
|
||||
}
|
||||
|
||||
type Csv struct {
|
||||
Tasks []*TaskList
|
||||
Path string
|
||||
Bridge *Tunnel
|
||||
RunList map[string]interface{}
|
||||
Hosts []*HostList //域名列表
|
||||
}
|
||||
|
||||
func (s *Csv) Init() {
|
||||
s.LoadTaskFromCsv()
|
||||
s.LoadHostFromCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) StoreTasksToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(s.Path + "tasks.csv")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
defer csvFile.Close()
|
||||
writer := csv.NewWriter(csvFile)
|
||||
for _, task := range s.Tasks {
|
||||
record := []string{
|
||||
strconv.Itoa(task.TcpPort),
|
||||
task.Mode,
|
||||
task.Target,
|
||||
task.VerifyKey,
|
||||
task.U,
|
||||
task.P,
|
||||
task.Compress,
|
||||
strconv.Itoa(task.Start),
|
||||
}
|
||||
err := writer.Write(record)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadTaskFromCsv() {
|
||||
// 打开文件
|
||||
file, err := os.Open(s.Path + "tasks.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 获取csv的reader
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
// 设置FieldsPerRecord为-1
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
// 读取文件中所有行保存到slice中
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var tasks []*TaskList
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
tcpPort, _ := strconv.Atoi(item[0])
|
||||
Start, _ := strconv.Atoi(item[7])
|
||||
post := &TaskList{
|
||||
TcpPort: tcpPort,
|
||||
Mode: item[1],
|
||||
Target: item[2],
|
||||
VerifyKey: item[3],
|
||||
U: item[4],
|
||||
P: item[5],
|
||||
Compress: item[6],
|
||||
Start: Start,
|
||||
}
|
||||
tasks = append(tasks, post)
|
||||
}
|
||||
s.Tasks = tasks
|
||||
}
|
||||
|
||||
func (s *Csv) StoreHostToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(s.Path + "hosts.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer csvFile.Close()
|
||||
// 获取csv的Writer
|
||||
writer := csv.NewWriter(csvFile)
|
||||
// 将map中的Post转换成slice,因为csv的Write需要slice参数
|
||||
// 并写入csv文件
|
||||
for _, host := range s.Hosts {
|
||||
record := []string{
|
||||
host.Host,
|
||||
host.Target,
|
||||
host.Vkey,
|
||||
}
|
||||
err1 := writer.Write(record)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
}
|
||||
// 确保所有内存数据刷到csv文件
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadHostFromCsv() {
|
||||
// 打开文件
|
||||
file, err := os.Open(s.Path + "hosts.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 获取csv的reader
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
// 设置FieldsPerRecord为-1
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
// 读取文件中所有行保存到slice中
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var hosts []*HostList
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &HostList{
|
||||
Vkey: item[2],
|
||||
Host: item[0],
|
||||
Target: item[1],
|
||||
}
|
||||
hosts = append(hosts, post)
|
||||
}
|
||||
s.Hosts = hosts
|
||||
}
|
||||
|
||||
func (s *Csv) GetTaskList(start, length int, typeVal string) ([]*TaskList, int) {
|
||||
list := make([]*TaskList, 0)
|
||||
var cnt int
|
||||
for _, v := range s.Tasks {
|
||||
if v.Mode != typeVal {
|
||||
continue
|
||||
}
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length > 0 {
|
||||
if _, ok := s.RunList[v.VerifyKey]; ok {
|
||||
v.IsRun = 1
|
||||
} else {
|
||||
v.IsRun = 0
|
||||
}
|
||||
if s, ok := s.Bridge.signalList[getverifyval(v.VerifyKey)]; ok {
|
||||
if s.Len() > 0 {
|
||||
v.ClientStatus = 1
|
||||
} else {
|
||||
v.ClientStatus = 0
|
||||
}
|
||||
} else {
|
||||
v.ClientStatus = 0
|
||||
}
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return list, cnt
|
||||
}
|
||||
|
||||
func (s *Csv) NewTask(t *TaskList) {
|
||||
s.Tasks = append(s.Tasks, t)
|
||||
s.StoreTasksToCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateTask(t *TaskList) error {
|
||||
for k, v := range s.Tasks {
|
||||
if v.VerifyKey == t.VerifyKey {
|
||||
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
|
||||
s.Tasks = append(s.Tasks, t)
|
||||
s.StoreTasksToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
//TODO:待测试
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) AddRunList(vKey string, svr interface{}) {
|
||||
s.RunList[vKey] = svr
|
||||
}
|
||||
|
||||
func (s *Csv) DelRunList(vKey string) {
|
||||
delete(s.RunList, vKey)
|
||||
}
|
||||
|
||||
func (s *Csv) DelTask(vKey string) error {
|
||||
for k, v := range s.Tasks {
|
||||
if v.VerifyKey == vKey {
|
||||
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
|
||||
s.StoreTasksToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) GetTask(vKey string) (v *TaskList, err error) {
|
||||
for _, v = range s.Tasks {
|
||||
if v.VerifyKey == vKey {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("未找到")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Csv) DelHost(host string) error {
|
||||
for k, v := range s.Hosts {
|
||||
if v.Host == host {
|
||||
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
|
||||
s.StoreHostToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) NewHost(t *HostList) {
|
||||
s.Hosts = append(s.Hosts, t)
|
||||
s.StoreHostToCsv()
|
||||
|
||||
}
|
||||
|
||||
func (s *Csv) GetHostList(start, length int, vKey string) ([]*HostList, int) {
|
||||
list := make([]*HostList, 0)
|
||||
var cnt int
|
||||
for _, v := range s.Hosts {
|
||||
if v.Vkey == vKey {
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length > 0 {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, cnt
|
||||
}
|
165
lib/init.go
Normal file
165
lib/init.go
Normal file
@ -0,0 +1,165 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
configPath = flag.String("config", "config.json", "配置文件路径")
|
||||
TcpPort = flag.Int("tcpport", 8284, "客户端与服务端通信端口")
|
||||
httpPort = flag.Int("httpport", 8024, "对外监听的端口")
|
||||
rpMode = flag.String("mode", "client", "启动模式")
|
||||
tunnelTarget = flag.String("target", "10.1.50.203:80", "远程目标")
|
||||
verifyKey = flag.String("vkey", "", "验证密钥")
|
||||
u = flag.String("u", "", "socks5验证用户名")
|
||||
p = flag.String("p", "", "socks5验证密码")
|
||||
compress = flag.String("compress", "", "数据压缩方式(gzip|snappy)")
|
||||
serverAddr = flag.String("server", "", "服务器地址ip:端口")
|
||||
config Config
|
||||
err error
|
||||
RunList map[string]interface{} //运行中的任务
|
||||
bridge *Tunnel
|
||||
CsvDb *Csv
|
||||
)
|
||||
|
||||
func init() {
|
||||
RunList = make(map[string]interface{})
|
||||
}
|
||||
|
||||
func InitMode() {
|
||||
flag.Parse()
|
||||
de, en := getCompressType(*compress)
|
||||
if *rpMode == "client" {
|
||||
JsonParse := NewJsonStruct()
|
||||
if config, err = JsonParse.Load(*configPath); err != nil {
|
||||
log.Println("配置文件加载失败")
|
||||
}
|
||||
stop := make(chan int)
|
||||
for _, v := range strings.Split(*verifyKey, ",") {
|
||||
log.Println("客户端启动,连接:", *serverAddr, " 验证令牌:", v)
|
||||
go NewRPClient(*serverAddr, 2, v).Start()
|
||||
}
|
||||
<-stop
|
||||
} else {
|
||||
bridge = newTunnel(*TcpPort)
|
||||
if err := bridge.StartTunnel(); err != nil {
|
||||
log.Fatalln("服务端开启失败", err)
|
||||
}
|
||||
log.Println("服务端启动,监听tcp服务端端口:", *TcpPort)
|
||||
if svr := newMode(*rpMode, bridge, *httpPort, *tunnelTarget, *u, *p, en, de, *verifyKey); svr != nil {
|
||||
reflect.ValueOf(svr).MethodByName("Start").Call(nil)
|
||||
} else {
|
||||
log.Fatalln("启动模式不正确")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//从csv文件中恢复任务
|
||||
func InitFromCsv() {
|
||||
for _, v := range CsvDb.Tasks {
|
||||
if v.Start == 1 {
|
||||
log.Println(""+
|
||||
"启动模式:", v.Mode, "监听端口:", v.TcpPort, "客户端令牌:", v.VerifyKey)
|
||||
AddTask(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newMode(mode string, bridge *Tunnel, httpPort int, tunnelTarget string, u string, p string, enCompress int, deCompress int, vkey string) interface{} {
|
||||
switch mode {
|
||||
case "httpServer":
|
||||
return NewHttpModeServer(httpPort, bridge, enCompress, deCompress, vkey)
|
||||
case "tunnelServer":
|
||||
return NewTunnelModeServer(httpPort, tunnelTarget, ProcessTunnel, bridge, enCompress, deCompress, vkey)
|
||||
case "sock5Server":
|
||||
return NewSock5ModeServer(httpPort, u, p, bridge, enCompress, deCompress, vkey)
|
||||
case "httpProxyServer":
|
||||
return NewTunnelModeServer(httpPort, tunnelTarget, ProcessHttp, bridge, enCompress, deCompress, vkey)
|
||||
case "udpServer":
|
||||
return NewUdpModeServer(httpPort, tunnelTarget, bridge, enCompress, deCompress, vkey)
|
||||
case "webServer":
|
||||
InitCsvDb()
|
||||
return NewWebServer(bridge)
|
||||
case "hostServer":
|
||||
return NewHostServer()
|
||||
case "httpHostServer":
|
||||
return NewTunnelModeServer(httpPort, tunnelTarget, ProcessHost, bridge, enCompress, deCompress, vkey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StopServer(cFlag string) error {
|
||||
if v, ok := RunList[cFlag]; ok {
|
||||
reflect.ValueOf(v).MethodByName("Close").Call(nil)
|
||||
delete(RunList, cFlag)
|
||||
if t := bridge.signalList[getverifyval(cFlag)]; t != nil {
|
||||
if *verifyKey == "" { //多客户端模式重启相关隧道
|
||||
for {
|
||||
if t.Len() <= 0 {
|
||||
break
|
||||
}
|
||||
t.Pop().Close()
|
||||
}
|
||||
delete(bridge.signalList, getverifyval(cFlag))
|
||||
delete(bridge.tunnelList, getverifyval(cFlag))
|
||||
}
|
||||
}
|
||||
if t, err := CsvDb.GetTask(cFlag); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.Start = 0
|
||||
CsvDb.UpdateTask(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("未在运行中")
|
||||
}
|
||||
|
||||
func AddTask(t *TaskList) error {
|
||||
de, en := getCompressType(t.Compress)
|
||||
if svr := newMode(t.Mode, bridge, t.TcpPort, t.Target, t.U, t.P, en, de, t.VerifyKey); svr != nil {
|
||||
RunList[t.VerifyKey] = svr
|
||||
go func() {
|
||||
err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
|
||||
if err.Interface() != nil {
|
||||
log.Println("客户端", t.VerifyKey, "启动失败,错误:", err)
|
||||
delete(RunList, t.VerifyKey)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
return errors.New("启动模式不正确")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartTask(vKey string) error {
|
||||
if t, err := CsvDb.GetTask(vKey); err != nil {
|
||||
return err
|
||||
} else {
|
||||
AddTask(t)
|
||||
t.Start = 1
|
||||
CsvDb.UpdateTask(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DelTask(vKey string) error {
|
||||
if err := StopServer(vKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return CsvDb.DelTask(vKey)
|
||||
}
|
||||
|
||||
func InitCsvDb() *Csv {
|
||||
var once sync.Once
|
||||
once.Do(func() {
|
||||
CsvDb = NewCsv("./conf/", bridge, RunList)
|
||||
CsvDb.Init()
|
||||
})
|
||||
return CsvDb
|
||||
}
|
290
lib/server.go
Executable file
290
lib/server.go
Executable file
@ -0,0 +1,290 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
VERIFY_EER = "vkey"
|
||||
WORK_MAIN = "main"
|
||||
WORK_CHAN = "chan"
|
||||
RES_SIGN = "sign"
|
||||
RES_MSG = "msg0"
|
||||
TEST_FLAG = "tst"
|
||||
CONN_TCP = "tcp"
|
||||
CONN_UDP = "udp"
|
||||
)
|
||||
|
||||
type HttpModeServer struct {
|
||||
bridge *Tunnel
|
||||
httpPort int
|
||||
enCompress int
|
||||
deCompress int
|
||||
vKey string
|
||||
}
|
||||
|
||||
func NewHttpModeServer(httpPort int, bridge *Tunnel, enCompress int, deCompress int, vKey string) *HttpModeServer {
|
||||
s := new(HttpModeServer)
|
||||
s.bridge = bridge
|
||||
s.httpPort = httpPort
|
||||
s.enCompress = enCompress
|
||||
s.deCompress = deCompress
|
||||
s.vKey = vKey
|
||||
return s
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *HttpModeServer) Start() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
retry:
|
||||
err, conn := s.bridge.GetSignal(getverifyval(s.vKey))
|
||||
if err != nil {
|
||||
BadRequest(w)
|
||||
}
|
||||
if err := s.writeRequest(r, conn); err != nil {
|
||||
log.Println(err)
|
||||
conn.Close()
|
||||
goto retry
|
||||
return
|
||||
}
|
||||
err = s.writeResponse(w, conn)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
conn.Close()
|
||||
goto retry
|
||||
return
|
||||
}
|
||||
s.bridge.ReturnSignal(conn, getverifyval(s.vKey))
|
||||
})
|
||||
log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", s.httpPort), nil))
|
||||
}
|
||||
|
||||
//req转为bytes发送给client端
|
||||
func (s *HttpModeServer) writeRequest(r *http.Request, conn *Conn) error {
|
||||
raw, err := EncodeRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.wSign()
|
||||
conn.WriteCompressType(s.enCompress, s.deCompress)
|
||||
c, err := conn.WriteCompress(raw, s.enCompress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != len(raw) {
|
||||
return errors.New("写出长度与字节长度不一致。")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//从client读取出Response
|
||||
func (s *HttpModeServer) writeResponse(w http.ResponseWriter, c *Conn) error {
|
||||
flags, err := c.ReadFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch flags {
|
||||
case RES_SIGN:
|
||||
buf := make([]byte, 1024*32)
|
||||
n, err := c.ReadFromCompress(buf, s.deCompress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := DecodeResponse(buf[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range resp.Header {
|
||||
for _, v2 := range v {
|
||||
w.Header().Set(k, v2)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
w.Write(bodyBytes)
|
||||
case RES_MSG:
|
||||
BadRequest(w)
|
||||
return errors.New("客户端请求出错")
|
||||
default:
|
||||
BadRequest(w)
|
||||
return errors.New("无法解析此错误")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type process func(c *Conn, s *TunnelModeServer) error
|
||||
|
||||
type TunnelModeServer struct {
|
||||
httpPort int
|
||||
tunnelTarget string
|
||||
process process
|
||||
bridge *Tunnel
|
||||
listener *net.TCPListener
|
||||
enCompress int
|
||||
deCompress int
|
||||
vKey string
|
||||
}
|
||||
|
||||
func NewTunnelModeServer(httpPort int, tunnelTarget string, process process, bridge *Tunnel, enCompress int, deCompress int, vKey string) *TunnelModeServer {
|
||||
s := new(TunnelModeServer)
|
||||
s.httpPort = httpPort
|
||||
s.bridge = bridge
|
||||
s.tunnelTarget = tunnelTarget
|
||||
s.process = process
|
||||
s.enCompress = enCompress
|
||||
s.deCompress = deCompress
|
||||
s.vKey = vKey
|
||||
return s
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *TunnelModeServer) Start() error {
|
||||
s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.httpPort, ""})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
conn, err := s.listener.AcceptTCP()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
break
|
||||
}
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go s.process(NewConn(conn), s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TunnelModeServer) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
//tcp隧道模式
|
||||
func ProcessTunnel(c *Conn, s *TunnelModeServer) error {
|
||||
link := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress)
|
||||
if _, err := link.WriteHost(CONN_TCP, s.tunnelTarget); err != nil {
|
||||
link.Close()
|
||||
c.Close()
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
go relay(link, c, s.enCompress)
|
||||
relay(c, link, s.deCompress)
|
||||
return nil
|
||||
}
|
||||
|
||||
//http代理模式
|
||||
func ProcessHttp(c *Conn, s *TunnelModeServer) error {
|
||||
method, addr, rb, err := c.GetHost()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return err
|
||||
}
|
||||
link := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress)
|
||||
if _, err := link.WriteHost(CONN_TCP, addr); err != nil {
|
||||
c.Close()
|
||||
link.Close()
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
if method == "CONNECT" {
|
||||
fmt.Fprint(c, "HTTP/1.1 200 Connection established\r\n")
|
||||
} else {
|
||||
link.WriteCompress(rb, s.enCompress)
|
||||
}
|
||||
go relay(link, c, s.enCompress)
|
||||
relay(c, link, s.deCompress)
|
||||
return nil
|
||||
}
|
||||
|
||||
//多客户端域名代理
|
||||
func ProcessHost(c *Conn, s *TunnelModeServer) error {
|
||||
method, addr, rb, err := c.GetHost()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return err
|
||||
}
|
||||
host, task, err := getKeyByHost(addr)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return err
|
||||
}
|
||||
de, en := getCompressType(task.Compress)
|
||||
link := s.bridge.GetTunnel(getverifyval(host.Vkey), en, de)
|
||||
if _, err := link.WriteHost(CONN_TCP, host.Target); err != nil {
|
||||
c.Close()
|
||||
link.Close()
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
if method == "CONNECT" {
|
||||
fmt.Fprint(c, "HTTP/1.1 200 Connection established\r\n")
|
||||
} else {
|
||||
link.WriteCompress(rb, en)
|
||||
}
|
||||
go relay(link, c, en)
|
||||
relay(c, link, de)
|
||||
return nil
|
||||
}
|
||||
|
||||
//web管理方式
|
||||
type WebServer struct {
|
||||
bridge *Tunnel
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *WebServer) Start() {
|
||||
InitFromCsv()
|
||||
p, _ := beego.AppConfig.Int("hostPort")
|
||||
t := &TaskList{
|
||||
TcpPort: p,
|
||||
Mode: "httpHostServer",
|
||||
Target: "",
|
||||
VerifyKey: "",
|
||||
U: "",
|
||||
P: "",
|
||||
Compress: "",
|
||||
Start: 1,
|
||||
IsRun: 0,
|
||||
ClientStatus: 0,
|
||||
}
|
||||
AddTask(t)
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
log.Println("web管理启动,访问端口为",beego.AppConfig.String("httpport"))
|
||||
beego.Run()
|
||||
}
|
||||
|
||||
func NewWebServer(bridge *Tunnel) *WebServer {
|
||||
s := new(WebServer)
|
||||
s.bridge = bridge
|
||||
return s
|
||||
}
|
||||
|
||||
//host
|
||||
type HostServer struct {
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *HostServer) Start() error {
|
||||
return nil
|
||||
}
|
||||
func NewHostServer() *HostServer {
|
||||
s := new(HostServer)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *HostServer) Close() error {
|
||||
return nil
|
||||
}
|
53
sock5.go → lib/sock5.go
Normal file → Executable file
53
sock5.go → lib/sock5.go
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,11 +44,15 @@ const (
|
||||
)
|
||||
|
||||
type Sock5ModeServer struct {
|
||||
Tunnel
|
||||
httpPort int
|
||||
u string //用户名
|
||||
p string //密码
|
||||
isVerify bool
|
||||
bridge *Tunnel
|
||||
httpPort int
|
||||
u string //用户名
|
||||
p string //密码
|
||||
enCompress int
|
||||
deCompress int
|
||||
isVerify bool
|
||||
listener net.Listener
|
||||
vKey string
|
||||
}
|
||||
|
||||
func (s *Sock5ModeServer) handleRequest(c net.Conn) {
|
||||
@ -131,7 +136,7 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) (proxyConn *Conn,
|
||||
binary.Read(c, binary.BigEndian, &port)
|
||||
// connect to host
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
client := s.GetTunnel()
|
||||
client := s.bridge.GetTunnel(getverifyval(s.vKey),s.enCompress,s.deCompress)
|
||||
s.sendReply(c, succeeded)
|
||||
var ltype string
|
||||
if command == associateMethod {
|
||||
@ -149,8 +154,8 @@ func (s *Sock5ModeServer) handleConnect(c net.Conn) {
|
||||
log.Println(err)
|
||||
c.Close()
|
||||
} else {
|
||||
go relay(proxyConn, NewConn(c), DataEncode)
|
||||
go relay(NewConn(c), proxyConn, DataDecode)
|
||||
go relay(proxyConn, NewConn(c), s.enCompress)
|
||||
go relay(NewConn(c), proxyConn, s.deCompress)
|
||||
}
|
||||
|
||||
}
|
||||
@ -182,8 +187,8 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
} else {
|
||||
go relay(proxyConn, NewConn(c), DataEncode)
|
||||
go relay(NewConn(c), proxyConn, DataDecode)
|
||||
go relay(proxyConn, NewConn(c), s.enCompress)
|
||||
go relay(NewConn(c), proxyConn, s.deCompress)
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,27 +263,32 @@ func (s *Sock5ModeServer) Auth(c net.Conn) error {
|
||||
return errors.New("未知错误")
|
||||
}
|
||||
|
||||
func (s *Sock5ModeServer) Start() {
|
||||
l, err := net.Listen("tcp", ":"+strconv.Itoa(s.httpPort))
|
||||
func (s *Sock5ModeServer) Start() error {
|
||||
s.listener, err = net.Listen("tcp", ":"+strconv.Itoa(s.httpPort))
|
||||
if err != nil {
|
||||
log.Fatal("listen error: ", err)
|
||||
return err
|
||||
}
|
||||
s.StartTunnel()
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
break
|
||||
}
|
||||
log.Fatal("accept error: ", err)
|
||||
}
|
||||
go s.handleNewConn(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSock5ModeServer(tcpPort, httpPort int, u, p string) *Sock5ModeServer {
|
||||
func (s *Sock5ModeServer) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func NewSock5ModeServer(httpPort int, u, p string, brige *Tunnel, enCompress int, deCompress int, vKey string) *Sock5ModeServer {
|
||||
s := new(Sock5ModeServer)
|
||||
s.tunnelPort = tcpPort
|
||||
s.httpPort = httpPort
|
||||
s.tunnelList = make(chan *Conn, 1000)
|
||||
s.signalList = make(chan *Conn, 10)
|
||||
s.bridge = brige
|
||||
if u != "" && p != "" {
|
||||
s.isVerify = true
|
||||
s.u = u
|
||||
@ -286,5 +296,8 @@ func NewSock5ModeServer(tcpPort, httpPort int, u, p string) *Sock5ModeServer {
|
||||
} else {
|
||||
s.isVerify = false
|
||||
}
|
||||
s.enCompress = enCompress
|
||||
s.deCompress = deCompress
|
||||
s.vKey = vKey
|
||||
return s
|
||||
}
|
184
lib/tunnel.go
Executable file
184
lib/tunnel.go
Executable file
@ -0,0 +1,184 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type list struct {
|
||||
connList chan *Conn
|
||||
}
|
||||
|
||||
func (l *list) Add(c *Conn) {
|
||||
l.connList <- c
|
||||
}
|
||||
|
||||
func (l *list) Pop() *Conn {
|
||||
return <-l.connList
|
||||
}
|
||||
func (l *list) Len() int {
|
||||
return len(l.connList)
|
||||
}
|
||||
|
||||
func newList() *list {
|
||||
l := new(list)
|
||||
l.connList = make(chan *Conn, 100)
|
||||
return l
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
tunnelPort int //通信隧道端口
|
||||
listener *net.TCPListener //server端监听
|
||||
signalList map[string]*list //通信
|
||||
tunnelList map[string]*list //隧道
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func newTunnel(tunnelPort int) *Tunnel {
|
||||
t := new(Tunnel)
|
||||
t.tunnelPort = tunnelPort
|
||||
t.signalList = make(map[string]*list)
|
||||
t.tunnelList = make(map[string]*list)
|
||||
return t
|
||||
}
|
||||
|
||||
func (s *Tunnel) StartTunnel() error {
|
||||
var err error
|
||||
s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.tunnelPort, ""})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go s.tunnelProcess()
|
||||
return nil
|
||||
}
|
||||
|
||||
//tcp server
|
||||
func (s *Tunnel) tunnelProcess() error {
|
||||
var err error
|
||||
for {
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go s.cliProcess(NewConn(conn))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//验证失败,返回错误验证flag,并且关闭连接
|
||||
func (s *Tunnel) verifyError(c *Conn) {
|
||||
c.conn.Write([]byte(VERIFY_EER))
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
func (s *Tunnel) cliProcess(c *Conn) error {
|
||||
c.conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(5) * time.Second))
|
||||
vval := make([]byte, 32)
|
||||
if _, err := c.conn.Read(vval); err != nil {
|
||||
log.Println("客户端读超时。客户端地址为::", c.conn.RemoteAddr())
|
||||
c.conn.Close()
|
||||
return err
|
||||
}
|
||||
//TODO:暂时取消
|
||||
if !verify(string(vval)) {
|
||||
log.Println("当前客户端连接校验错误,关闭此客户端:", c.conn.RemoteAddr())
|
||||
s.verifyError(c)
|
||||
return err
|
||||
}
|
||||
c.conn.(*net.TCPConn).SetReadDeadline(time.Time{})
|
||||
//做一个判断 添加到对应的channel里面以供使用
|
||||
if flag, err := c.ReadFlag(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return s.typeDeal(flag, c, string(vval))
|
||||
}
|
||||
}
|
||||
|
||||
//tcp连接类型区分
|
||||
func (s *Tunnel) typeDeal(typeVal string, c *Conn, cFlag string) error {
|
||||
switch typeVal {
|
||||
case WORK_MAIN:
|
||||
s.addList(s.signalList, c, cFlag)
|
||||
case WORK_CHAN:
|
||||
s.addList(s.tunnelList, c, cFlag)
|
||||
default:
|
||||
return errors.New("无法识别")
|
||||
}
|
||||
c.SetAlive()
|
||||
return nil
|
||||
}
|
||||
|
||||
//加到对应的list中
|
||||
func (s *Tunnel) addList(m map[string]*list, c *Conn, cFlag string) {
|
||||
s.Lock()
|
||||
if v, ok := m[cFlag]; ok {
|
||||
v.Add(c)
|
||||
} else {
|
||||
l := newList()
|
||||
l.Add(c)
|
||||
m[cFlag] = l
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
//新建隧道
|
||||
func (s *Tunnel) newChan(cFlag string) {
|
||||
s.wait(s.signalList, cFlag)
|
||||
retry:
|
||||
connPass := s.signalList[cFlag].Pop()
|
||||
_, err := connPass.conn.Write([]byte("chan"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
goto retry
|
||||
}
|
||||
s.signalList[cFlag].Add(connPass)
|
||||
}
|
||||
|
||||
//得到一个tcp隧道
|
||||
func (s *Tunnel) GetTunnel(cFlag string, en, de int) *Conn {
|
||||
if v, ok := s.tunnelList[cFlag]; !ok || v.Len() < 10 { //新建通道
|
||||
go s.newChan(cFlag)
|
||||
}
|
||||
retry:
|
||||
s.wait(s.tunnelList, cFlag)
|
||||
c := s.tunnelList[cFlag].Pop()
|
||||
if _, err := c.wTest(); err != nil {
|
||||
c.Close()
|
||||
goto retry
|
||||
}
|
||||
c.WriteCompressType(en, de)
|
||||
return c
|
||||
}
|
||||
|
||||
//得到一个通信通道
|
||||
func (s *Tunnel) GetSignal(cFlag string) (err error, conn *Conn) {
|
||||
if v, ok := s.signalList[cFlag]; !ok || v.Len() == 0 {
|
||||
err = errors.New("客户端未连接")
|
||||
return
|
||||
}
|
||||
conn = s.signalList[cFlag].Pop()
|
||||
return
|
||||
}
|
||||
|
||||
//重回slice 复用
|
||||
func (s *Tunnel) ReturnSignal(conn *Conn, cFlag string) {
|
||||
if v, ok := s.signalList[cFlag]; ok {
|
||||
v.Add(conn)
|
||||
}
|
||||
}
|
||||
|
||||
//等待
|
||||
func (s *Tunnel) wait(m map[string]*list, cFlag string) {
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
for {
|
||||
<-ticker.C
|
||||
if _, ok := m[cFlag]; ok {
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
81
lib/udp.go
Executable file
81
lib/udp.go
Executable file
@ -0,0 +1,81 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UdpModeServer struct {
|
||||
bridge *Tunnel
|
||||
udpPort int //监听的udp端口
|
||||
tunnelTarget string //udp目标地址
|
||||
listener *net.UDPConn
|
||||
udpMap map[string]*Conn
|
||||
enCompress int
|
||||
deCompress int
|
||||
vKey string
|
||||
}
|
||||
|
||||
func NewUdpModeServer(udpPort int, tunnelTarget string, bridge *Tunnel, enCompress int, deCompress int, vKey string) *UdpModeServer {
|
||||
s := new(UdpModeServer)
|
||||
s.udpPort = udpPort
|
||||
s.tunnelTarget = tunnelTarget
|
||||
s.bridge = bridge
|
||||
s.udpMap = make(map[string]*Conn)
|
||||
s.enCompress = enCompress
|
||||
s.deCompress = deCompress
|
||||
s.vKey = vKey
|
||||
return s
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *UdpModeServer) Start() error {
|
||||
s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), s.udpPort, ""})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := make([]byte, 1472) //udp数据包大小
|
||||
for {
|
||||
n, addr, err := s.listener.ReadFromUDP(data)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
break
|
||||
}
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go s.process(addr, data[:n])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO:效率问题有待解决
|
||||
func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
|
||||
fmt.Println(addr.String())
|
||||
fmt.Println(string(data))
|
||||
conn := s.bridge.GetTunnel(getverifyval(s.vKey),s.enCompress,s.deCompress)
|
||||
if _, err := conn.WriteHost(CONN_UDP, s.tunnelTarget); err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.WriteCompress(data, s.enCompress)
|
||||
go func(addr *net.UDPAddr, conn *Conn) {
|
||||
buf := make([]byte, 1024)
|
||||
conn.conn.SetReadDeadline(time.Now().Add(time.Duration(time.Second * 3)))
|
||||
n, err := conn.ReadFromCompress(buf, s.deCompress)
|
||||
if err != nil || err == io.EOF {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
s.listener.WriteToUDP(buf[:n], addr)
|
||||
conn.Close()
|
||||
}(addr, conn)
|
||||
}
|
||||
|
||||
func (s *UdpModeServer) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|
164
util.go → lib/util.go
Normal file → Executable file
164
util.go → lib/util.go
Normal file → Executable file
@ -1,20 +1,27 @@
|
||||
package main
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang/snappy"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -130,6 +137,7 @@ func relay(in, out *Conn, compressType int) {
|
||||
buf := make([]byte, 32*1024)
|
||||
switch compressType {
|
||||
case COMPRESS_GZIP_ENCODE:
|
||||
//TODO:GZIP压缩存在问题有待解决
|
||||
w := gzip.NewWriter(in)
|
||||
for {
|
||||
n, err := out.Read(buf)
|
||||
@ -146,32 +154,140 @@ func relay(in, out *Conn, compressType int) {
|
||||
}
|
||||
w.Close()
|
||||
case COMPRESS_SNAPY_ENCODE:
|
||||
w := snappy.NewBufferedWriter(in)
|
||||
for {
|
||||
n, err := out.Read(buf)
|
||||
if err != nil || err == io.EOF {
|
||||
break
|
||||
}
|
||||
if _, err = w.Write(buf[:n]); err != nil {
|
||||
break
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
w.Close()
|
||||
io.Copy(NewSnappyConn(in.conn), out)
|
||||
case COMPRESS_GZIP_DECODE:
|
||||
r, err := gzip.NewReader(out)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
io.Copy(in, r)
|
||||
io.Copy(in, NewGzipConn(out.conn))
|
||||
case COMPRESS_SNAPY_DECODE:
|
||||
r := snappy.NewReader(out)
|
||||
io.Copy(in, r)
|
||||
io.Copy(in, NewSnappyConn(out.conn))
|
||||
default:
|
||||
io.Copy(in, out)
|
||||
}
|
||||
out.Close()
|
||||
in.Close()
|
||||
}
|
||||
|
||||
type Site struct {
|
||||
Host string
|
||||
Url string
|
||||
Port int
|
||||
}
|
||||
type Config struct {
|
||||
SiteList []Site
|
||||
Replace int
|
||||
}
|
||||
type JsonStruct struct {
|
||||
}
|
||||
|
||||
func NewJsonStruct() *JsonStruct {
|
||||
return &JsonStruct{}
|
||||
}
|
||||
func (jst *JsonStruct) Load(filename string) (Config, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
config := Config{}
|
||||
if err != nil {
|
||||
return config, errors.New("配置文件打开错误")
|
||||
}
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return config, errors.New("配置文件解析错误")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
//判断压缩方式
|
||||
func getCompressType(compress string) (int, int) {
|
||||
switch compress {
|
||||
case "":
|
||||
return COMPRESS_NONE, COMPRESS_NONE
|
||||
case "gzip":
|
||||
return COMPRESS_GZIP_DECODE, COMPRESS_GZIP_ENCODE
|
||||
case "snappy":
|
||||
return COMPRESS_SNAPY_DECODE, COMPRESS_SNAPY_ENCODE
|
||||
default:
|
||||
log.Fatalln("数据压缩格式错误")
|
||||
}
|
||||
return COMPRESS_NONE, COMPRESS_NONE
|
||||
}
|
||||
|
||||
// 简单的一个校验值
|
||||
func getverifyval(vkey string) string {
|
||||
//单客户端模式
|
||||
if *verifyKey != "" {
|
||||
return Md5(*verifyKey)
|
||||
}
|
||||
return Md5(vkey)
|
||||
}
|
||||
|
||||
func verify(verifyKeyMd5 string) bool {
|
||||
if getverifyval(*verifyKey) == verifyKeyMd5 {
|
||||
return true
|
||||
}
|
||||
if *verifyKey == "" {
|
||||
for _, v := range CsvDb.Tasks {
|
||||
if _, ok := RunList[v.VerifyKey]; getverifyval(v.VerifyKey) == verifyKeyMd5 && ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getKeyByHost(host string) (h *HostList, t *TaskList, err error) {
|
||||
for _, v := range CsvDb.Hosts {
|
||||
if strings.Contains(host, v.Host) {
|
||||
h = v
|
||||
t, err = CsvDb.GetTask(v.Vkey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("未找到host对应的内网目标")
|
||||
return
|
||||
}
|
||||
|
||||
//生成32位md5字串
|
||||
func Md5(s string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
//生成随机验证密钥
|
||||
func GetRandomString(l int) string {
|
||||
str := "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
bytes := []byte(str)
|
||||
result := []byte{}
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < l; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func Gethostbyname(hostname string) string {
|
||||
if !DomainCheck(hostname) {
|
||||
return hostname
|
||||
}
|
||||
ips, _ := net.LookupIP(hostname)
|
||||
if ips != nil {
|
||||
for _, v := range ips {
|
||||
if v.To4() != nil {
|
||||
return v.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func DomainCheck(domain string) bool {
|
||||
var match bool
|
||||
IsLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/)"
|
||||
NotLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}"
|
||||
match, _ = regexp.MatchString(IsLine, domain)
|
||||
if !match {
|
||||
match, _ = regexp.MatchString(NotLine, domain)
|
||||
}
|
||||
return match
|
||||
}
|
77
main.go
77
main.go
@ -1,81 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
configPath = flag.String("config", "config.json", "配置文件路径")
|
||||
tcpPort = flag.Int("tcpport", 8284, "Socket连接或者监听的端口")
|
||||
httpPort = flag.Int("httpport", 8024, "当mode为server时为服务端监听端口,当为mode为client时为转发至本地客户端的端口")
|
||||
rpMode = flag.String("mode", "client", "启动模式,可选为client|server")
|
||||
tunnelTarget = flag.String("target", "10.1.50.203:80", "远程目标")
|
||||
verifyKey = flag.String("vkey", "", "验证密钥")
|
||||
u = flag.String("u", "", "socks5验证用户名")
|
||||
p = flag.String("p", "", "socks5验证密码")
|
||||
compress = flag.String("compress", "", "数据压缩(gzip|snappy)")
|
||||
config Config
|
||||
err error
|
||||
DataEncode int
|
||||
DataDecode int
|
||||
"github.com/cnlh/easyProxy/lib"
|
||||
_ "github.com/cnlh/easyProxy/routers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||
switch *compress {
|
||||
case "":
|
||||
DataDecode = COMPRESS_NONE
|
||||
DataEncode = COMPRESS_NONE
|
||||
case "gzip":
|
||||
DataDecode = COMPRESS_GZIP_DECODE
|
||||
DataEncode = COMPRESS_GZIP_ENCODE
|
||||
case "snnapy":
|
||||
DataDecode = COMPRESS_SNAPY_DECODE
|
||||
DataEncode = COMPRESS_SNAPY_ENCODE
|
||||
default:
|
||||
log.Fatalln("数据压缩格式错误")
|
||||
}
|
||||
if *rpMode == "client" {
|
||||
JsonParse := NewJsonStruct()
|
||||
config, err = JsonParse.Load(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
*verifyKey = config.Server.Vkey
|
||||
log.Println("客户端启动,连接:", config.Server.Ip, ", 端口:", config.Server.Tcp)
|
||||
cli := NewRPClient(fmt.Sprintf("%s:%d", config.Server.Ip, config.Server.Tcp), config.Server.Num)
|
||||
cli.Start()
|
||||
} else {
|
||||
if *verifyKey == "" {
|
||||
log.Fatalln("必须输入一个验证的key")
|
||||
}
|
||||
if *tcpPort <= 0 || *tcpPort >= 65536 {
|
||||
log.Fatalln("请输入正确的tcp端口。")
|
||||
}
|
||||
if *httpPort <= 0 || *httpPort >= 65536 {
|
||||
log.Fatalln("请输入正确的http端口。")
|
||||
}
|
||||
log.Println("服务端启动,监听tcp服务端端口:", *tcpPort, ", 外部服务端端口:", *httpPort)
|
||||
if *rpMode == "httpServer" {
|
||||
svr := NewHttpModeServer(*tcpPort, *httpPort)
|
||||
if err := svr.Start(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
} else if *rpMode == "tunnelServer" {
|
||||
svr := NewTunnelModeServer(*tcpPort, *httpPort, *tunnelTarget, ProcessTunnel)
|
||||
svr.Start()
|
||||
} else if *rpMode == "sock5Server" {
|
||||
svr := NewSock5ModeServer(*tcpPort, *httpPort, *u, *p)
|
||||
svr.Start()
|
||||
} else if *rpMode == "httpProxyServer" {
|
||||
svr := NewTunnelModeServer(*tcpPort, *httpPort, *tunnelTarget, ProcessHttp)
|
||||
svr.Start()
|
||||
} else if *rpMode == "udpServer" {
|
||||
svr := NewUdpModeServer(*tcpPort, *httpPort, *tunnelTarget)
|
||||
svr.Start()
|
||||
}
|
||||
}
|
||||
lib.InitMode()
|
||||
}
|
||||
|
12
routers/router.go
Executable file
12
routers/router.go
Executable file
@ -0,0 +1,12 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/cnlh/easyProxy/controllers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
beego.Router("/", &controllers.IndexController{}, "*:Index")
|
||||
beego.AutoRouter(&controllers.IndexController{})
|
||||
beego.AutoRouter(&controllers.LoginController{})
|
||||
}
|
212
server.go
212
server.go
@ -1,212 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
VERIFY_EER = "vkey"
|
||||
WORK_MAIN = "main"
|
||||
WORK_CHAN = "chan"
|
||||
RES_SIGN = "sign"
|
||||
RES_MSG = "msg0"
|
||||
TEST_FLAG = "tst"
|
||||
CONN_TCP = "tcp"
|
||||
CONN_UDP = "udp"
|
||||
)
|
||||
|
||||
type HttpModeServer struct {
|
||||
Tunnel
|
||||
httpPort int
|
||||
}
|
||||
|
||||
func NewHttpModeServer(tcpPort, httpPort int) *HttpModeServer {
|
||||
s := new(HttpModeServer)
|
||||
s.tunnelPort = tcpPort
|
||||
s.httpPort = httpPort
|
||||
s.signalList = make(chan *Conn, 1000)
|
||||
return s
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *HttpModeServer) Start() (error) {
|
||||
err := s.StartTunnel()
|
||||
if err != nil {
|
||||
log.Fatalln("开启客户端失败!", err)
|
||||
return err
|
||||
}
|
||||
s.startHttpServer()
|
||||
return nil
|
||||
}
|
||||
|
||||
//开启http端口监听
|
||||
func (s *HttpModeServer) startHttpServer() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
retry:
|
||||
if len(s.signalList) == 0 {
|
||||
BadRequest(w)
|
||||
return
|
||||
}
|
||||
conn := <-s.signalList
|
||||
if err := s.writeRequest(r, conn); err != nil {
|
||||
log.Println(err)
|
||||
conn.Close()
|
||||
goto retry
|
||||
return
|
||||
}
|
||||
err = s.writeResponse(w, conn)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
conn.Close()
|
||||
goto retry
|
||||
return
|
||||
}
|
||||
s.signalList <- conn
|
||||
})
|
||||
log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", s.httpPort), nil))
|
||||
}
|
||||
|
||||
//req转为bytes发送给client端
|
||||
func (s *HttpModeServer) writeRequest(r *http.Request, conn *Conn) error {
|
||||
raw, err := EncodeRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.wSign()
|
||||
c, err := conn.WriteCompress(raw, DataEncode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != len(raw) {
|
||||
return errors.New("写出长度与字节长度不一致。")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//从client读取出Response
|
||||
func (s *HttpModeServer) writeResponse(w http.ResponseWriter, c *Conn) error {
|
||||
flags, err := c.ReadFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch flags {
|
||||
case RES_SIGN:
|
||||
buf := make([]byte, 1024*32)
|
||||
n, err := c.ReadFromCompress(buf, DataDecode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := DecodeResponse(buf[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range resp.Header {
|
||||
for _, v2 := range v {
|
||||
w.Header().Set(k, v2)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
w.Write(bodyBytes)
|
||||
case RES_MSG:
|
||||
BadRequest(w)
|
||||
return errors.New("客户端请求出错")
|
||||
default:
|
||||
BadRequest(w)
|
||||
return errors.New("无法解析此错误")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type process func(c *Conn, s *TunnelModeServer) error
|
||||
type TunnelModeServer struct {
|
||||
Tunnel
|
||||
httpPort int
|
||||
tunnelTarget string
|
||||
process process
|
||||
}
|
||||
|
||||
func NewTunnelModeServer(tcpPort, httpPort int, tunnelTarget string, process process) *TunnelModeServer {
|
||||
s := new(TunnelModeServer)
|
||||
s.tunnelPort = tcpPort
|
||||
s.httpPort = httpPort
|
||||
s.tunnelTarget = tunnelTarget
|
||||
s.tunnelList = make(chan *Conn, 1000)
|
||||
s.signalList = make(chan *Conn, 10)
|
||||
s.process = process
|
||||
return s
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *TunnelModeServer) Start() (error) {
|
||||
err := s.StartTunnel()
|
||||
if err != nil {
|
||||
log.Fatalln("开启客户端失败!", err)
|
||||
return err
|
||||
}
|
||||
s.startTunnelServer()
|
||||
return nil
|
||||
}
|
||||
|
||||
//隧道模式server
|
||||
func (s *TunnelModeServer) startTunnelServer() {
|
||||
listener, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.httpPort, ""})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for {
|
||||
conn, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go s.process(NewConn(conn), s)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:这种实现方式……
|
||||
//tcp隧道模式
|
||||
func ProcessTunnel(c *Conn, s *TunnelModeServer) error {
|
||||
link := s.GetTunnel()
|
||||
if _, err := link.WriteHost(CONN_TCP, s.tunnelTarget); err != nil {
|
||||
link.Close()
|
||||
c.Close()
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
go relay(link, c, DataEncode)
|
||||
relay(c, link, DataDecode)
|
||||
return nil
|
||||
}
|
||||
|
||||
//http代理模式
|
||||
func ProcessHttp(c *Conn, s *TunnelModeServer) error {
|
||||
method, addr, rb, err := c.GetHost()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return err
|
||||
}
|
||||
link := s.GetTunnel()
|
||||
if _, err := link.WriteHost("tcp", addr); err != nil {
|
||||
c.Close()
|
||||
link.Close()
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
if method == "CONNECT" {
|
||||
fmt.Fprint(c, "HTTP/1.1 200 Connection established\r\n")
|
||||
} else {
|
||||
link.WriteCompress(rb, DataEncode)
|
||||
}
|
||||
go relay(link, c, DataEncode)
|
||||
relay(c, link, DataDecode)
|
||||
return nil
|
||||
}
|
4
static/css/font-awesome.min.css
vendored
Executable file
4
static/css/font-awesome.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
15240
static/css/main.css
Executable file
15240
static/css/main.css
Executable file
File diff suppressed because it is too large
Load Diff
BIN
static/fonts/FontAwesome.otf
Executable file
BIN
static/fonts/FontAwesome.otf
Executable file
Binary file not shown.
BIN
static/fonts/fontawesome-webfont.eot
Executable file
BIN
static/fonts/fontawesome-webfont.eot
Executable file
Binary file not shown.
2671
static/fonts/fontawesome-webfont.svg
Executable file
2671
static/fonts/fontawesome-webfont.svg
Executable file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
BIN
static/fonts/fontawesome-webfont.ttf
Executable file
BIN
static/fonts/fontawesome-webfont.ttf
Executable file
Binary file not shown.
BIN
static/fonts/fontawesome-webfont.woff
Executable file
BIN
static/fonts/fontawesome-webfont.woff
Executable file
Binary file not shown.
BIN
static/fonts/fontawesome-webfont.woff2
Executable file
BIN
static/fonts/fontawesome-webfont.woff2
Executable file
Binary file not shown.
BIN
static/img/48.jpg
Normal file
BIN
static/img/48.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
static/img/favicon.ico
Executable file
BIN
static/img/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
194
static/js/datatables.min.js
vendored
Executable file
194
static/js/datatables.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
27
static/js/main.js
Executable file
27
static/js/main.js
Executable file
@ -0,0 +1,27 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var treeviewMenu = $('.app-menu');
|
||||
|
||||
// Toggle Sidebar
|
||||
$('[data-toggle="sidebar"]').click(function(event) {
|
||||
event.preventDefault();
|
||||
$('.app').toggleClass('sidenav-toggled');
|
||||
});
|
||||
|
||||
// Activate sidebar treeview toggle
|
||||
$("[data-toggle='treeview']").click(function(event) {
|
||||
event.preventDefault();
|
||||
if(!$(this).parent().hasClass('is-expanded')) {
|
||||
treeviewMenu.find("[data-toggle='treeview']").parent().removeClass('is-expanded');
|
||||
}
|
||||
$(this).parent().toggleClass('is-expanded');
|
||||
});
|
||||
|
||||
// Set initial active toggle
|
||||
$("[data-toggle='treeview.'].is-expanded").parent().toggleClass('is-expanded');
|
||||
|
||||
//Activate bootstrip tooltips
|
||||
// $("[data-toggle='tooltip']").tooltip();
|
||||
|
||||
})();
|
111
tunnel.go
111
tunnel.go
@ -1,111 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Tunnel struct {
|
||||
tunnelPort int //通信隧道端口
|
||||
listener *net.TCPListener //server端监听
|
||||
signalList chan *Conn //通信
|
||||
tunnelList chan *Conn //隧道
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *Tunnel) StartTunnel() error {
|
||||
var err error
|
||||
s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.tunnelPort, ""})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go s.tunnelProcess()
|
||||
return nil
|
||||
}
|
||||
|
||||
//tcp server
|
||||
func (s *Tunnel) tunnelProcess() error {
|
||||
var err error
|
||||
for {
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go s.cliProcess(NewConn(conn))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//验证失败,返回错误验证flag,并且关闭连接
|
||||
func (s *Tunnel) verifyError(c *Conn) {
|
||||
c.conn.Write([]byte(VERIFY_EER))
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
func (s *Tunnel) cliProcess(c *Conn) error {
|
||||
c.conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(5) * time.Second))
|
||||
vval := make([]byte, 20)
|
||||
_, err := c.conn.Read(vval)
|
||||
if err != nil {
|
||||
log.Println("客户端读超时。客户端地址为::", c.conn.RemoteAddr())
|
||||
c.conn.Close()
|
||||
return err
|
||||
}
|
||||
if bytes.Compare(vval, getverifyval()[:]) != 0 {
|
||||
log.Println("当前客户端连接校验错误,关闭此客户端:", c.conn.RemoteAddr())
|
||||
s.verifyError(c)
|
||||
return err
|
||||
}
|
||||
c.conn.(*net.TCPConn).SetReadDeadline(time.Time{})
|
||||
//做一个判断 添加到对应的channel里面以供使用
|
||||
flag, err := c.ReadFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.typeDeal(flag, c)
|
||||
}
|
||||
|
||||
//tcp连接类型区分
|
||||
func (s *Tunnel) typeDeal(typeVal string, c *Conn) error {
|
||||
switch typeVal {
|
||||
case WORK_MAIN:
|
||||
s.signalList <- c
|
||||
case WORK_CHAN:
|
||||
s.tunnelList <- c
|
||||
default:
|
||||
return errors.New("无法识别")
|
||||
}
|
||||
c.SetAlive()
|
||||
return nil
|
||||
}
|
||||
|
||||
//新建隧道
|
||||
func (s *Tunnel) newChan() {
|
||||
retry:
|
||||
connPass := <-s.signalList
|
||||
_, err := connPass.conn.Write([]byte("chan"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
goto retry
|
||||
}
|
||||
s.signalList <- connPass
|
||||
}
|
||||
|
||||
func (s *Tunnel) GetTunnel() *Conn {
|
||||
if len(s.tunnelList) < 10 { //新建通道
|
||||
go s.newChan()
|
||||
}
|
||||
retry:
|
||||
c := <-s.tunnelList
|
||||
_, err := c.wTest()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
goto retry
|
||||
}
|
||||
return c
|
||||
}
|
83
udp.go
83
udp.go
@ -1,83 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UdpModeServer struct {
|
||||
Tunnel
|
||||
udpPort int //监听的udp端口
|
||||
tunnelTarget string //udp目标地址
|
||||
listener *net.UDPConn
|
||||
udpMap map[string]*Conn
|
||||
}
|
||||
|
||||
func NewUdpModeServer(tcpPort, udpPort int, tunnelTarget string) *UdpModeServer {
|
||||
s := new(UdpModeServer)
|
||||
s.tunnelPort = tcpPort
|
||||
s.udpPort = udpPort
|
||||
s.tunnelTarget = tunnelTarget
|
||||
s.tunnelList = make(chan *Conn, 1000)
|
||||
s.signalList = make(chan *Conn, 10)
|
||||
s.udpMap = make(map[string]*Conn)
|
||||
return s
|
||||
}
|
||||
|
||||
//开始
|
||||
func (s *UdpModeServer) Start() (error) {
|
||||
err := s.StartTunnel()
|
||||
if err != nil {
|
||||
log.Fatalln("启动失败!", err)
|
||||
return err
|
||||
}
|
||||
s.startTunnelServer()
|
||||
return nil
|
||||
}
|
||||
|
||||
//udp监听
|
||||
func (s *UdpModeServer) startTunnelServer() {
|
||||
s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), s.udpPort, ""})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
data := make([]byte, 1472) //udp数据包大小
|
||||
for {
|
||||
n, addr, err := s.listener.ReadFromUDP(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go s.process(addr, data[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
|
||||
conn := s.GetTunnel()
|
||||
if _, err := conn.WriteHost(CONN_UDP, s.tunnelTarget);err!=nil{
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
conn.conn.SetReadDeadline(time.Now().Add(time.Duration(time.Second * 3)))
|
||||
n, err := conn.ReadFromCompress(buf, DataDecode)
|
||||
if err != nil || err == io.EOF {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
_, err = s.listener.WriteToUDP(buf[:n], addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
if _, err = conn.WriteCompress(data, DataEncode); err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
12
verify.go
12
verify.go
@ -1,12 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 简单的一个校验值
|
||||
func getverifyval() []byte {
|
||||
b := sha1.Sum([]byte(time.Now().Format("2006-01-02 15") + *verifyKey))
|
||||
return b[:]
|
||||
}
|
99
views/index/add.html
Executable file
99
views/index/add.html
Executable file
@ -0,0 +1,99 @@
|
||||
<div class="row tile">
|
||||
<div class="col-lg-12">
|
||||
<div class="bs-component">
|
||||
<div class="alert alert-dismissible alert-success">
|
||||
<button class="close" type="button" data-dismiss="alert">×</button>
|
||||
<span id="info"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-md-auto">
|
||||
<div>
|
||||
<h3 class="tile-title">添加</h3>
|
||||
<div class="tile-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label class="control-label">模式</label>
|
||||
<select class="form-control" name="type" id="type">
|
||||
<option {{if eq "tunnelServer" .type}}selected{{end}} value="tunnelServer">tcp隧道</option>
|
||||
<option {{if eq "udpServer" .type}}selected{{end}} value="udpServer">udp隧道</option>
|
||||
<option {{if eq "sock5Server" .type}}selected{{end}} value="sock5Server">socks5代理</option>
|
||||
<option {{if eq "httpProxyServer" .type}}selected{{end}} value="httpProxyServer">http代理
|
||||
<option {{if eq "hostServer" .type}}selected{{end}} value="hostServer">host客户端</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="port">
|
||||
<label class="control-label">监听的端口</label>
|
||||
<input class="form-control" type="text" name="port" placeholder="公网服务器对外访问端口,例如8024">
|
||||
</div>
|
||||
<div class="form-group" id="target">
|
||||
<label class="control-label">内网目标(ip:端口)</label>
|
||||
<input class="form-control" type="text" name="target" placeholder="内网代理地址,例如10.1.50.203:22">
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">数据压缩方式</label>
|
||||
<select class="form-control" name="compress">
|
||||
<option value="">不压缩</option>
|
||||
<option value="gzip">gzip压缩</option>
|
||||
<option value="snappy">snappy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="u">
|
||||
<label class="control-label">socks5代理模式验证用户名</label>
|
||||
<input class="form-control" type="text" name="u" placeholder="不填则无需验证">
|
||||
</div>
|
||||
<div class="form-group" id="p">
|
||||
<label class="control-label">socks5代理模式验证密码</label>
|
||||
<input class="form-control" type="text" name="p" placeholder="不填则无需验证">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tile-footer">
|
||||
<button class="btn btn-success" href="#" id="add"><i
|
||||
class="fa fa-fw fa-lg fa-eye"></i>添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
var arr = []
|
||||
arr["all"] = ["type", "port", "compress", "u", "p", "target"]
|
||||
arr["tunnelServer"] = ["type", "port", "target", "compress", "tcp隧道模式,提供一条tcp隧道,适用于ssh、远程桌面等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的目标端口"]
|
||||
arr["udpServer"] = ["type", "port", "target", "compress", "udp隧道模式,提供一条udp隧道,适用于dns、内网dns访问等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的udp目标端口"]
|
||||
arr["sock5Server"] = ["type", "port", "compress", "u", "p", "socks5代理模式,内网socks5代理,配合proxifer,可如同使用vpn一样访问内网设备或资源,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置socks5代理,即访问内网设备或者资源 "]
|
||||
arr["httpProxyServer"] = ["type", "port", "compress", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
|
||||
arr["hostServer"] = ["type", "compress", "域名分发模式,使用域名代理内网服务,适用于小程序开发、公众号开发、站点演示等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,使用nginx将请求反向代理到本程序,再进行域名配置,即可解析"]
|
||||
|
||||
function resetForm() {
|
||||
for (var i = 0; i < arr["all"].length; i++) {
|
||||
$("#" + arr["all"][i]).css("display", "none")
|
||||
}
|
||||
o = $("#type option:selected").val()
|
||||
for (var i = 0; i < arr[o].length - 1; i++) {
|
||||
$("#" + arr[o][i]).css("display", "block")
|
||||
}
|
||||
$("#info").html(arr[o][arr[o].length - 1])
|
||||
}
|
||||
|
||||
$(function () {
|
||||
resetForm()
|
||||
$("#type").on("change", function () {
|
||||
resetForm()
|
||||
})
|
||||
$("#add").on("click", function () {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/add",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
history.back(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
94
views/index/edit.html
Executable file
94
views/index/edit.html
Executable file
@ -0,0 +1,94 @@
|
||||
<div class="row tile">
|
||||
<div class="col-md-6 col-md-auto">
|
||||
<div>
|
||||
<h3 class="tile-title">添加</h3>
|
||||
<div class="tile-body">
|
||||
<form>
|
||||
<input type="hidden" name="vKey" value="{{.t.VerifyKey}}">
|
||||
<div class="form-group">
|
||||
<label class="control-label">模式</label>
|
||||
<select class="form-control" name="type" id="type">
|
||||
<option {{if eq "tunnelServer" .t.Mode}}selected{{end}} value="tunnelServer">tcp隧道</option>
|
||||
<option {{if eq "udpServer" .t.Mode}}selected{{end}} value="udpServer">udp隧道</option>
|
||||
<option {{if eq "sock5Server" .t.Mode}}selected{{end}} value="sock5Server">socks5代理</option>
|
||||
<option {{if eq "httpProxyServer" .t.Mode}}selected{{end}} value="httpProxyServer">http代理
|
||||
<option {{if eq "hostServer" .t.Mode}}selected{{end}} value="hostServer">host客户端</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="port">
|
||||
<label class="control-label">监听的端口</label>
|
||||
<input class="form-control" value="{{.t.TcpPort}}" type="text" name="port"
|
||||
placeholder="公网服务器对外访问端口,例如8024">
|
||||
</div>
|
||||
<div class="form-group" id="target">
|
||||
<label class="control-label">内网目标(仅tcp、udp隧道模式需填写)</label>
|
||||
<input class="form-control" value="{{.t.Target}}" type="text" name="target"
|
||||
placeholder="内网隧道目标,例如10.1.50.203:22">
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">数据压缩方式(所有模式均支持)</label>
|
||||
<select class="form-control" name="compress">
|
||||
<option {{if eq "" .t.Compress}}selected{{end}} value="">不压缩</option>
|
||||
<option {{if eq "gzip" .t.Compress}}selected{{end}} value="gzip">gzip压缩</option>
|
||||
<option {{if eq "snappy" .t.Compress}}selected{{end}} value="snappy">snappy压缩</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="u">
|
||||
<label class="control-label">socks5代理模式验证用户名</label>
|
||||
<input class="form-control" value="{{.t.U}}" type="text" name="u"
|
||||
placeholder="不填则无需验证,非socks5模式不填">
|
||||
</div>
|
||||
<div class="form-group" id="p">
|
||||
<label class="control-label">socks5代理模式验证密码</label>
|
||||
<input class="form-control" value="{{.t.P}}" type="text" name="p"
|
||||
placeholder="不填则无需验证,非socks5模式不填">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tile-footer">
|
||||
<button class="btn btn-success" href="#" id="add"><i
|
||||
class="fa fa-fw fa-lg fa-eye"></i>保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
var arr = []
|
||||
arr["all"] = ["type", "port", "compress", "u", "p","target"]
|
||||
arr["tunnelServer"] = ["type", "port", "target", "compress"]
|
||||
arr["udpServer"] = ["type", "port", "target", "compress"]
|
||||
arr["sock5Server"] = ["type", "port", "compress", "u", "p"]
|
||||
arr["httpProxyServer"] = ["type", "port", "compress"]
|
||||
arr["hostServer"] = ["type", "compress"]
|
||||
|
||||
function resetForm() {
|
||||
for (var i = 0; i < arr["all"].length; i++) {
|
||||
$("#" + arr["all"][i]).css("display", "none")
|
||||
}
|
||||
o = $("#type option:selected").val()
|
||||
for (var i = 0; i < arr[o].length; i++) {
|
||||
$("#" + arr[o][i]).css("display", "block")
|
||||
}
|
||||
}
|
||||
$(function () {
|
||||
resetForm()
|
||||
$("#type").on("change", function () {
|
||||
resetForm()
|
||||
})
|
||||
$("#add").on("click", function () {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/edit",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
history.back(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
43
views/index/hadd.html
Executable file
43
views/index/hadd.html
Executable file
@ -0,0 +1,43 @@
|
||||
<div class="row tile">
|
||||
<div class="col-md-6 col-md-auto">
|
||||
<div>
|
||||
<h3 class="tile-title">添加</h3>
|
||||
<div class="tile-body">
|
||||
<form>
|
||||
<input type="hidden" name="vkey" value="{{.vkey}}">
|
||||
<div class="form-group">
|
||||
<label class="control-label">域名</label>
|
||||
<input class="form-control" type="text" name="host" placeholder="域名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">内网目标</label>
|
||||
<input class="form-control" type="text" name="target" placeholder="内网隧道目标,例如10.1.50.203:22">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tile-footer">
|
||||
<button class="btn btn-success" href="#" id="add"><i
|
||||
class="fa fa-fw fa-lg fa-eye"></i>添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
$(function () {
|
||||
$("#add").on("click", function () {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/addhost",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
history.back(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
81
views/index/hlist.html
Executable file
81
views/index/hlist.html
Executable file
@ -0,0 +1,81 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="tile">
|
||||
<div class="tile-body">
|
||||
<table class="table table-hover table-bordered" id="sampleTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>客户端key</th>
|
||||
<th>host</th>
|
||||
<th>内网目标</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script type="text/javascript">
|
||||
function del(host) {
|
||||
if (confirm("确定要删除数据吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/delhost",
|
||||
data: {"host": host},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function add() {
|
||||
window.location.href = "/index/addhost?vkey={{.vkey}}"
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var table = $('#sampleTable').DataTable({
|
||||
dom: 'Bfrtip',
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
autoWidth: false,
|
||||
ordering: false,
|
||||
ajax: {
|
||||
url: window.location,
|
||||
type: 'POST'
|
||||
},
|
||||
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
|
||||
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
|
||||
{data: 'Vkey'},
|
||||
{data: 'Host'},
|
||||
{data: 'Target'},
|
||||
{data: 'Target'},
|
||||
],
|
||||
bFilter: false,
|
||||
columnDefs: [{
|
||||
targets: -1,
|
||||
render: function (data, type, row, meta) {
|
||||
|
||||
return '<div class="btn-group" role="group" aria-label="..."> ' +
|
||||
'<button onclick="del(\'' + row.Host + '\')" type="button" class="btn btn-danger btn-sm">删除</button>' +
|
||||
' </div>'
|
||||
}
|
||||
}
|
||||
],
|
||||
buttons: []
|
||||
});
|
||||
$("#sampleTable_length").html('<button class="btn btn-primary" onclick="add()" type="button">新增</button>')
|
||||
})
|
||||
;
|
||||
|
||||
|
||||
</script>
|
152
views/index/index.html
Executable file
152
views/index/index.html
Executable file
@ -0,0 +1,152 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="tile">
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=star&count=true&size=large"
|
||||
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=watch&count=true&size=large&v=2"
|
||||
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=fork&count=true&size=large"
|
||||
frameborder="0" scrolling="0" width="158px" height="30px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="tile">
|
||||
<h3 class="tile-title">域名代理模式</h3>
|
||||
<p>
|
||||
<b>适用范围:</b> 小程序开发、微信公众号开发、产品演示
|
||||
</p>
|
||||
<p>
|
||||
<b>假设场景:</b>
|
||||
<li>有一个域名proxy.com,有一台公网机器ip为{{.ip}}</li>
|
||||
<li>两个内网开发站点127.0.0.1:81,127.0.0.1:82</li>
|
||||
<li>想通过a.proxy.com访问127.0.0.1:81,通过b.proxy.com访问127.0.0.1:82</li>
|
||||
</p>
|
||||
<p><b>使用步骤:</b></p>
|
||||
<ul>
|
||||
<li>将a.proxy.com,b.proxy.com解析到公网服务器{{.ip}}</li>
|
||||
<li>使用nginx监听这两个个域名,并配置ssl等……</li>
|
||||
<li>在nginx配置中添加反向代理<br>
|
||||
<pre><code>
|
||||
server {
|
||||
listen 80;
|
||||
server_name a.proxy.com b.proxy.com;#也可以是泛解析*.proxy.com
|
||||
#ssl等配置
|
||||
<b>location / {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Nginx-Proxy true;
|
||||
proxy_set_header Connection "";
|
||||
proxy_pass http://127.0.0.1:{{.proxyPort}};
|
||||
}</b>
|
||||
}
|
||||
</code></pre>
|
||||
</li>
|
||||
<li>在域名代理管理中添加一个客户端,选择压缩方式,保存。 <a href="/index/add?type=hostServer">立即添加</a></li>
|
||||
{{/*<li>在域名代理管理中找到新加的客户端(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
|
||||
<li>点击该客户端的域名管理,添加两条规则规则:1、域名:a.proxy.com,内网目标:127.0.0.1:81,2、域名:b.proxy.com,内网目标:127.0.0.1:82</li>
|
||||
<li>现在访问a.proxy.com,b.proxy.com即可成功</li>
|
||||
</ul>
|
||||
<p>注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认启动方式为单客户端模式,默认内网客户端已经启动</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="tile">
|
||||
<h3 class="tile-title">tcp隧道模式</h3>
|
||||
<p>
|
||||
<b>适用范围:</b> ssh、远程桌面等tcp连接场景
|
||||
</p>
|
||||
<p>
|
||||
<b>假设场景:</b> 想通过访问公网服务器{{.ip}}的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接
|
||||
</p>
|
||||
<p><b>使用步骤:</b></p>
|
||||
<ul>
|
||||
<li>在tcp隧道管理中添加一条隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),选择压缩方式,保存。 <a
|
||||
href="/index/add?type=tunnelServer">立即添加</a></li>
|
||||
{{/*<li>在tcp管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
|
||||
<li>访问公网服务器ip({{.ip}}):填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:ssh -p 8001 root@{{.ip}}</li>
|
||||
</ul>
|
||||
<p>注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认启动方式为单客户端模式,默认内网客户端已经启动</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="tile">
|
||||
<h3 class="tile-title">udp隧道模式</h3>
|
||||
<p>
|
||||
<b>适用范围:</b> 内网dns解析等udp连接场景
|
||||
</p>
|
||||
<p>
|
||||
<b>假设场景:</b> 内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为{{.ip}}
|
||||
</p>
|
||||
<p><b>使用步骤:</b></p>
|
||||
<ul>
|
||||
<li>在udp隧道管理中添加一条隧道,填写监听的端口(8002)、内网目标ip和目标端口(10.1.50.102:53),选择压缩方式,保存。 <a
|
||||
href="/index/add?type=udpServer">立即添加</a></li>
|
||||
{{/*<li>在udp管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
|
||||
<li>修改本机dns为{{.ip}},则相当于使用10.1.50.202作为dns服务器</li>
|
||||
</ul>
|
||||
<p>注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认启动方式为单客户端模式,默认内网客户端已经启动</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="tile">
|
||||
<h3 class="tile-title">socks5代理模式</h3>
|
||||
<p>
|
||||
<b>适用范围:</b> 在外网环境下如同使用vpn一样访问内网设备或者资源
|
||||
</p>
|
||||
<p>
|
||||
<b>假设场景:</b> 想将公网服务器{{.ip}}的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果
|
||||
</p>
|
||||
<p><b>使用步骤:</b></p>
|
||||
<ul>
|
||||
<li>在socks5隧道管理中添加一条隧道,填写监听的端口(8003),验证用户名和密码自行选择(建议先不填,部分客户端不支持,proxifer支持),选择压缩方式,保存。 <a
|
||||
href="/index/add?type=sock5Server">立即添加</a></li>
|
||||
{{/*<li>在socks5代理管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
|
||||
<li>在外网环境的本机配置socks5代理,ip为公网服务器ip({{.ip}}),端口为填写的监听端口(8003),即可畅享内网了</li>
|
||||
</ul>
|
||||
<p>注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认启动方式为单客户端模式,默认内网客户端已经启动</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="tile">
|
||||
<h3 class="tile-title">http代理模式</h3>
|
||||
<p>
|
||||
<b>适用范围:</b> 在外网环境下访问内网站点
|
||||
</p>
|
||||
<p>
|
||||
<b>假设场景:</b> 想将公网服务器{{.ip}}的8004端口作为http代理,访问内网网站
|
||||
</p>
|
||||
<p><b>使用步骤:</b></p>
|
||||
<ul>
|
||||
<li>在http隧道管理中添加一条隧道,填写监听的端口(8004),选择压缩方式,保存。 <a
|
||||
href="/index/add?type=httpProxyServer">立即添加</a></li>
|
||||
<li>在http代理管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>
|
||||
<li>在外网环境的本机配置http代理,ip为公网服务器ip({{.ip}}),端口为填写的监听端口(8004),即可访问了</li>
|
||||
</ul>
|
||||
<p>注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认启动方式为单客户端模式,默认内网客户端已经启动</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="tile">
|
||||
<p>
|
||||
<b>多客户端模式:</b>
|
||||
<li>服务端启动:./easyProxy -mode=webServer -tcpport=8284</li>
|
||||
<li>客户端启动:./easyProxy -server={{.ip}}:{{.p}} -vkey=xxx(见管理列表的客户端启动模式)</li>
|
||||
</p>
|
||||
<p><b>支持客户端同时建立多条隧道,例如单个通道时命令为./easyProxy -server={{.ip}}:{{.p}} -vkey=ccc,如果要支持另外一个隧道,则对应的执行命令为./easyProxy
|
||||
-server={{.ip}}:{{.p}} -vkey=ccc,ddd,即用逗号分隔开多个vkey,适用于所有模式!</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
174
views/index/list.html
Executable file
174
views/index/list.html
Executable file
@ -0,0 +1,174 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="tile">
|
||||
<div class="tile-body">
|
||||
<table class="table table-hover table-bordered" id="sampleTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{{/*<th>模式</th>*/}}
|
||||
<th>监听端口</th>
|
||||
<th>内网目标</th>
|
||||
<th>多客户端模式客户端执行命令</th>
|
||||
<th>压缩方式</th>
|
||||
<th>用户名</th>
|
||||
<th>密码</th>
|
||||
<th>客户端状态</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script type="text/javascript">
|
||||
function del(vKey) {
|
||||
if (confirm("确定要删除数据吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/del",
|
||||
data: {"vKey": vKey},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function start(vKey) {
|
||||
if (confirm("确定要开始任务吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/start",
|
||||
data: {"vKey": vKey},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function stop(vKey) {
|
||||
if (confirm("确定要暂停吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/stop",
|
||||
data: {"vKey": vKey},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function edit(vKey) {
|
||||
window.location.href = "/index/edit?vKey=" + vKey
|
||||
}
|
||||
|
||||
function add() {
|
||||
window.location.href = "/index/add?type=" +{{.type}}
|
||||
}
|
||||
|
||||
function hostList(vkey) {
|
||||
window.location.href = "/index/hostlist?vkey=" + vkey
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var table = $('#sampleTable').DataTable({
|
||||
dom: 'Bfrtip',
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
autoWidth: false,
|
||||
ordering: false,
|
||||
ajax: {
|
||||
url: '/index/gettasklist?type={{.type}}',
|
||||
type: 'POST'
|
||||
},
|
||||
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
|
||||
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
|
||||
// {data: 'Mode'},
|
||||
{data: 'TcpPort'},
|
||||
{data: 'Target'},
|
||||
{data: 'VerifyKey'},
|
||||
{data: 'Compress'},
|
||||
{data: 'U'},
|
||||
{data: 'P'},
|
||||
{data: 'ClientStatus'},
|
||||
{data: 'IsRun'},
|
||||
{data: "Id"}
|
||||
],
|
||||
bFilter: false,
|
||||
columnDefs: [{
|
||||
targets: -1,
|
||||
render: function (data, type, row, meta) {
|
||||
if (row.IsRun == 1) {
|
||||
btn = "<button onclick=\"stop('" + row.VerifyKey + "')\" class=\"btn btn-secondary btn-sm\" type=\"button\">关闭</button>"
|
||||
} else {
|
||||
btn = "<button onclick=\"start('" + row.VerifyKey + "')\" class=\"btn btn-success btn-sm\" type=\"button\">打开</button>"
|
||||
}
|
||||
btn_edit = '<button onclick="edit(\'' + row.VerifyKey + '\')" type="button" class="btn btn-primary btn-sm">查看编辑</button> '
|
||||
if ({{.type}} == "hostServer"
|
||||
)
|
||||
{
|
||||
btn_host = '<button onclick="hostList(\'' + row.VerifyKey + '\')" type="button" class="btn btn-info btn-sm">域名管理</button> '
|
||||
}
|
||||
else
|
||||
{
|
||||
btn_host = ""
|
||||
}
|
||||
return '<div class="btn-group" role="group" aria-label="..."> ' +
|
||||
'<button onclick="del(\'' + row.VerifyKey + '\')" type="button" class="btn btn-danger btn-sm">删除</button>' +
|
||||
btn_edit + btn + btn_host + ' </div>'
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -2,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == 0) {
|
||||
return "<span class=\"badge badge-pill badge-secondary\">暂停</span>"
|
||||
} else {
|
||||
return "<span class=\"badge badge-pill badge-success\">正常</span>"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: 2,
|
||||
render: function (data, type, row, meta) {
|
||||
return "./easyProxy -server={{.ip}}:{{.p}} -vkey=" + data
|
||||
// return data
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -3,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == 0) {
|
||||
return "<span class=\"badge badge-pill badge-secondary\">离线</span>"
|
||||
} else {
|
||||
return "<span class=\"badge badge-pill badge-success\">在线</span>"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
buttons: []
|
||||
});
|
||||
$("#sampleTable_length").html('<button class="btn btn-primary" onclick="add()" type="button">新增</button>')
|
||||
})
|
||||
;
|
||||
|
||||
|
||||
</script>
|
60
views/login/index.html
Executable file
60
views/login/index.html
Executable file
@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="/static/img/favicon.ico">
|
||||
<!-- Main CSS-->
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/main.css">
|
||||
<!-- Font-icon css-->
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/font-awesome.min.css">
|
||||
<title>easyProxy内网穿透</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="material-half-bg">
|
||||
<div class="cover"></div>
|
||||
</section>
|
||||
<section class="login-content">
|
||||
<div class="logo">
|
||||
<h1></h1>
|
||||
</div>
|
||||
<div class="login-box">
|
||||
<form class="login-form" onsubmit="return false">
|
||||
<h3 class="login-head"><i class="fa fa-lg fa-fw fa-user"></i>内网穿透管理登陆</h3>
|
||||
<div class="form-group">
|
||||
<label class="control-label">密码</label>
|
||||
<input class="form-control" name="psd" type="password" placeholder="" autofocus>
|
||||
</div>
|
||||
<div class="form-group btn-container">
|
||||
<button onclick="login()" class="btn btn-primary btn-block"><i class="fa fa-sign-in fa-lg fa-fw"></i>登陆
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script type="text/javascript" src="/static/js/pdfmake.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/vfs_fonts.js"></script>
|
||||
<script type="text/javascript" src="/static/js/datatables.min.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Login Page Flipbox control
|
||||
function login() {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/login/verify",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
window.location.href = "/index/index"
|
||||
} else {
|
||||
alert(res.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
5
views/public/error.html
Executable file
5
views/public/error.html
Executable file
@ -0,0 +1,5 @@
|
||||
<div class="page-error tile">
|
||||
<h1><i class="fa fa-exclamation-circle"></i> Error 404: Page not found</h1>
|
||||
<p>The page you have requested is not found.</p>
|
||||
<p><a class="btn btn-primary" href="javascript:window.history.back();">Go Back</a></p>
|
||||
</div>
|
72
views/public/layout.html
Executable file
72
views/public/layout.html
Executable file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="description"
|
||||
content="Vali is a responsive and free admin theme built with Bootstrap 4, SASS and PUG.js. It's fully customizable and modular.">
|
||||
<link rel="shortcut icon" href="/static/img/favicon.ico">
|
||||
<title>easyProxy内网穿透</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Font-icon css-->
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/font-awesome.min.css">
|
||||
<!-- Main CSS-->
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/main.css">
|
||||
</head>
|
||||
<body class="app sidebar-mini rtl">
|
||||
<!-- Navbar-->
|
||||
<header class="app-header">
|
||||
<a class="app-header__logo" href="/"></a>
|
||||
<!-- Sidebar toggle button--><a class="app-sidebar__toggle" href="#" data-toggle="sidebar"
|
||||
aria-label="Hide Sidebar"></a>
|
||||
<ul class="app-nav">
|
||||
<!--Notification Menu-->
|
||||
<!-- User Menu-->
|
||||
<li class="dropdown"><a class="app-nav__item" href="/login/out" data-toggle="dropdown"
|
||||
aria-label="Open Profile Menu"><i class="fa fa-sign-out fa-lg"></i>退出登陆</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<!-- Sidebar menu-->
|
||||
<div class="app-sidebar__overlay" data-toggle="sidebar"></div>
|
||||
<aside class="app-sidebar">
|
||||
<div class="app-sidebar__user"><img class="app-sidebar__user-avatar"
|
||||
src="/static/img/48.jpg"
|
||||
alt="User Image">
|
||||
<div>
|
||||
<p class="app-sidebar__user-name">System</p>
|
||||
<p class="app-sidebar__user-designation">欢迎使用</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="app-menu">
|
||||
<li><a class="app-menu__item {{if eq "index" .menu}}active{{end}}" href="/"><i
|
||||
class="app-menu__icon fa fa-dashboard"></i><span class="app-menu__label">使用说明</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "host" .menu}}active{{end}}" href="/index/host"><i
|
||||
class="app-menu__icon fa fa-lemon-o"></i><span class="app-menu__label">域名代理</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "tcp" .menu}}active{{end}}" href="/index/tcp"><i
|
||||
class="app-menu__icon fa fa-life-buoy"></i><span class="app-menu__label">tcp隧道管理</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "udp" .menu}}active{{end}}" href="/index/udp"><i
|
||||
class="app-menu__icon fa fa-laptop"></i><span class="app-menu__label">udp隧道管理</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "socks5" .menu}}active{{end}}" href="/index/socks5"><i
|
||||
class="app-menu__icon fa fa-lightbulb-o"></i><span class="app-menu__label">socks5代理</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "http" .menu}}active{{end}}" href="/index/http"><i
|
||||
class="app-menu__icon fa fa-magic"></i><span class="app-menu__label">http代理</span></a></li>
|
||||
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<script type="text/javascript" src="/static/js/datatables.min.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
<main class="app-content">
|
||||
<div class="app-title">
|
||||
<div>
|
||||
<h1><i class="fa fa-th-list"></i> {{.name}}</h1>
|
||||
<a href="javascript:history.back(-1);"><i class="fa fa-mail-reply"></i>返回</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{.LayoutContent}}
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user