mirror of https://github.com/ehang-io/nps
dashboard 备注 客户端管理优化 多客户端支持 流量显示支持 热更新支持 404错误页支持
parent
c533436c78
commit
c34e5e1a7d
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/cnlh/easyProxy/utils"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
@ -31,20 +32,20 @@ func newList() *list {
|
|||
}
|
||||
|
||||
type Tunnel struct {
|
||||
TunnelPort int //通信隧道端口
|
||||
listener *net.TCPListener //server端监听
|
||||
SignalList map[string]*list //通信
|
||||
TunnelList map[string]*list //隧道
|
||||
RunList map[string]interface{} //运行中的任务
|
||||
TunnelPort int //通信隧道端口
|
||||
listener *net.TCPListener //server端监听
|
||||
SignalList map[int]*list //通信
|
||||
TunnelList map[int]*list //隧道
|
||||
RunList map[int]interface{} //运行中的任务
|
||||
lock sync.Mutex
|
||||
tunnelLock sync.Mutex
|
||||
}
|
||||
|
||||
func NewTunnel(tunnelPort int, runList map[string]interface{}) *Tunnel {
|
||||
func NewTunnel(tunnelPort int, runList map[int]interface{}) *Tunnel {
|
||||
t := new(Tunnel)
|
||||
t.TunnelPort = tunnelPort
|
||||
t.SignalList = make(map[string]*list)
|
||||
t.TunnelList = make(map[string]*list)
|
||||
t.SignalList = make(map[int]*list)
|
||||
t.TunnelList = make(map[int]*list)
|
||||
t.RunList = runList
|
||||
return t
|
||||
}
|
||||
|
@ -87,7 +88,8 @@ func (s *Tunnel) cliProcess(c *utils.Conn) error {
|
|||
c.Conn.Close()
|
||||
return err
|
||||
}
|
||||
if !s.verify(string(vval)) {
|
||||
id, err := utils.GetCsvDb().GetIdByVerifyKey(string(vval),c.Conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
log.Println("当前客户端连接校验错误,关闭此客户端:", c.Conn.RemoteAddr())
|
||||
s.verifyError(c)
|
||||
return errors.New("验证错误")
|
||||
|
@ -97,18 +99,18 @@ func (s *Tunnel) cliProcess(c *utils.Conn) error {
|
|||
if flag, err := c.ReadFlag(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return s.typeDeal(flag, c, string(vval))
|
||||
return s.typeDeal(flag, c, id)
|
||||
}
|
||||
}
|
||||
|
||||
//tcp连接类型区分
|
||||
func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, cFlag string) error {
|
||||
func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, id int) error {
|
||||
switch typeVal {
|
||||
case utils.WORK_MAIN:
|
||||
log.Println("客户端连接成功", c.Conn.RemoteAddr())
|
||||
s.addList(s.SignalList, c, cFlag)
|
||||
s.addList(s.SignalList, c, id)
|
||||
case utils.WORK_CHAN:
|
||||
s.addList(s.TunnelList, c, cFlag)
|
||||
s.addList(s.TunnelList, c, id)
|
||||
default:
|
||||
return errors.New("无法识别")
|
||||
}
|
||||
|
@ -117,41 +119,38 @@ func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, cFlag string) error {
|
|||
}
|
||||
|
||||
//加到对应的list中
|
||||
func (s *Tunnel) addList(m map[string]*list, c *utils.Conn, cFlag string) {
|
||||
func (s *Tunnel) addList(m map[int]*list, c *utils.Conn, id int) {
|
||||
s.lock.Lock()
|
||||
if v, ok := m[cFlag]; ok {
|
||||
if v, ok := m[id]; ok {
|
||||
v.Add(c)
|
||||
} else {
|
||||
l := newList()
|
||||
l.Add(c)
|
||||
m[cFlag] = l
|
||||
m[id] = l
|
||||
}
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
//新建隧道
|
||||
func (s *Tunnel) newChan(cFlag string) error {
|
||||
func (s *Tunnel) newChan(id int) error {
|
||||
var connPass *utils.Conn
|
||||
var err error
|
||||
retry:
|
||||
if connPass, err = s.waitAndPop(s.SignalList, cFlag); err != nil {
|
||||
if connPass, err = s.waitAndPop(s.SignalList, id); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = connPass.Conn.Write([]byte("chan")); err != nil {
|
||||
goto retry
|
||||
}
|
||||
s.SignalList[cFlag].Add(connPass)
|
||||
s.SignalList[id].Add(connPass)
|
||||
return nil
|
||||
}
|
||||
|
||||
//得到一个tcp隧道
|
||||
//TODO 超时问题 锁机制问题 对单个客户端加锁
|
||||
func (s *Tunnel) GetTunnel(cFlag string, en, de int, crypt, mux bool) (c *utils.Conn, err error) {
|
||||
if v, ok := s.TunnelList[cFlag]; !ok || v.Len() < 3 { //新建通道
|
||||
go s.newChan(cFlag)
|
||||
}
|
||||
func (s *Tunnel) GetTunnel(id int, en, de int, crypt, mux bool) (c *utils.Conn, err error) {
|
||||
retry:
|
||||
if c, err = s.waitAndPop(s.TunnelList, cFlag); err != nil {
|
||||
if c, err = s.waitAndPop(s.TunnelList, id); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.WriteTest(); err != nil {
|
||||
|
@ -163,61 +162,61 @@ retry:
|
|||
}
|
||||
|
||||
//得到一个通信通道
|
||||
func (s *Tunnel) GetSignal(cFlag string) (err error, conn *utils.Conn) {
|
||||
if v, ok := s.SignalList[cFlag]; !ok || v.Len() == 0 {
|
||||
func (s *Tunnel) GetSignal(id int) (err error, conn *utils.Conn) {
|
||||
if v, ok := s.SignalList[id]; !ok || v.Len() == 0 {
|
||||
err = errors.New("客户端未连接")
|
||||
return
|
||||
}
|
||||
conn = s.SignalList[cFlag].Pop()
|
||||
conn = s.SignalList[id].Pop()
|
||||
return
|
||||
}
|
||||
|
||||
//重回slice 复用
|
||||
func (s *Tunnel) ReturnSignal(conn *utils.Conn, cFlag string) {
|
||||
if v, ok := s.SignalList[cFlag]; ok {
|
||||
func (s *Tunnel) ReturnSignal(conn *utils.Conn, id int) {
|
||||
if v, ok := s.SignalList[id]; ok {
|
||||
v.Add(conn)
|
||||
}
|
||||
}
|
||||
|
||||
//重回slice 复用
|
||||
func (s *Tunnel) ReturnTunnel(conn *utils.Conn, cFlag string) {
|
||||
if v, ok := s.TunnelList[cFlag]; ok {
|
||||
func (s *Tunnel) ReturnTunnel(conn *utils.Conn, id int) {
|
||||
if v, ok := s.TunnelList[id]; ok {
|
||||
utils.FlushConn(conn.Conn)
|
||||
v.Add(conn)
|
||||
}
|
||||
}
|
||||
|
||||
//删除通信通道
|
||||
func (s *Tunnel) DelClientSignal(cFlag string) {
|
||||
s.delClient(cFlag, s.SignalList)
|
||||
func (s *Tunnel) DelClientSignal(id int) {
|
||||
s.delClient(id, s.SignalList)
|
||||
}
|
||||
|
||||
//删除隧道
|
||||
func (s *Tunnel) DelClientTunnel(cFlag string) {
|
||||
s.delClient(cFlag, s.TunnelList)
|
||||
func (s *Tunnel) DelClientTunnel(id int) {
|
||||
s.delClient(id, s.TunnelList)
|
||||
}
|
||||
|
||||
func (s *Tunnel) delClient(cFlag string, l map[string]*list) {
|
||||
if t := l[utils.Getverifyval(cFlag)]; t != nil {
|
||||
func (s *Tunnel) delClient(id int, l map[int]*list) {
|
||||
if t := l[id]; t != nil {
|
||||
for {
|
||||
if t.Len() <= 0 {
|
||||
break
|
||||
}
|
||||
t.Pop().Close()
|
||||
}
|
||||
delete(l, utils.Getverifyval(cFlag))
|
||||
delete(l, id)
|
||||
}
|
||||
}
|
||||
|
||||
//等待
|
||||
func (s *Tunnel) waitAndPop(m map[string]*list, cFlag string) (c *utils.Conn, err error) {
|
||||
func (s *Tunnel) waitAndPop(m map[int]*list, id int) (c *utils.Conn, err error) {
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
stop := time.After(time.Second * 10)
|
||||
stop := time.After(time.Second * 3)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.lock.Lock()
|
||||
if v, ok := m[cFlag]; ok && v.Len() > 0 {
|
||||
if v, ok := m[id]; ok && v.Len() > 0 {
|
||||
c = v.Pop()
|
||||
ticker.Stop()
|
||||
s.lock.Unlock()
|
||||
|
@ -225,16 +224,16 @@ func (s *Tunnel) waitAndPop(m map[string]*list, cFlag string) (c *utils.Conn, er
|
|||
}
|
||||
s.lock.Unlock()
|
||||
case <-stop:
|
||||
err = errors.New("client key: " + cFlag + ",err: get client conn timeout")
|
||||
err = errors.New("client id: " + strconv.Itoa(id) + ",err: get client conn timeout")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Tunnel) verify(vKeyMd5 string) bool {
|
||||
func (s *Tunnel) verify(id int) bool {
|
||||
for k := range s.RunList {
|
||||
if utils.Getverifyval(k) == vKeyMd5 {
|
||||
if k == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,16 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TRPClient struct {
|
||||
svrAddr string
|
||||
tcpNum int
|
||||
svrAddr string
|
||||
tcpNum int
|
||||
tunnelNum int64
|
||||
tunnel chan bool
|
||||
serverStatus bool
|
||||
sync.Mutex
|
||||
vKey string
|
||||
}
|
||||
|
@ -21,6 +25,7 @@ func NewRPClient(svraddr string, tcpNum int, vKey string) *TRPClient {
|
|||
c.svrAddr = svraddr
|
||||
c.tcpNum = tcpNum
|
||||
c.vKey = vKey
|
||||
c.tunnel = make(chan bool)
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -29,12 +34,17 @@ func (s *TRPClient) Start() error {
|
|||
for i := 0; i < s.tcpNum; i++ {
|
||||
go s.NewConn()
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
go s.dealChan()
|
||||
}
|
||||
go s.session()
|
||||
return nil
|
||||
}
|
||||
|
||||
//新建
|
||||
func (s *TRPClient) NewConn() error {
|
||||
s.Lock()
|
||||
s.serverStatus = false
|
||||
conn, err := net.Dial("tcp", s.svrAddr)
|
||||
if err != nil {
|
||||
log.Println("连接服务端失败,五秒后将重连")
|
||||
|
@ -44,11 +54,12 @@ func (s *TRPClient) NewConn() error {
|
|||
return err
|
||||
}
|
||||
s.Unlock()
|
||||
return s.process(utils.NewConn(conn))
|
||||
return s.processor(utils.NewConn(conn))
|
||||
}
|
||||
|
||||
//处理
|
||||
func (s *TRPClient) process(c *utils.Conn) error {
|
||||
func (s *TRPClient) processor(c *utils.Conn) error {
|
||||
s.serverStatus = true
|
||||
c.SetAlive()
|
||||
if _, err := c.Write([]byte(utils.Getverifyval(s.vKey))); err != nil {
|
||||
return err
|
||||
|
@ -58,7 +69,6 @@ func (s *TRPClient) process(c *utils.Conn) error {
|
|||
flags, err := c.ReadFlag()
|
||||
if err != nil {
|
||||
log.Println("服务端断开,五秒后将重连", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
go s.NewConn()
|
||||
break
|
||||
}
|
||||
|
@ -66,9 +76,6 @@ func (s *TRPClient) process(c *utils.Conn) error {
|
|||
case utils.VERIFY_EER:
|
||||
log.Fatalln("vkey:", s.vKey, "不正确,服务端拒绝连接,请检查")
|
||||
case utils.WORK_CHAN: //隧道模式,每次开启10个,加快连接速度
|
||||
for i := 0; i < 5; i++ {
|
||||
go s.dealChan()
|
||||
}
|
||||
case utils.RES_MSG:
|
||||
log.Println("服务端返回错误。")
|
||||
default:
|
||||
|
@ -98,13 +105,16 @@ func (s *TRPClient) dealChan() {
|
|||
//写标志
|
||||
c.WriteChan()
|
||||
re:
|
||||
atomic.AddInt64(&s.tunnelNum, 1)
|
||||
//获取连接的host type(tcp or udp)
|
||||
typeStr, host, en, de, crypt, mux, err := c.GetHostFromConn()
|
||||
s.tunnel <- true
|
||||
atomic.AddInt64(&s.tunnelNum, -1)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
Process(c, typeStr, host, en, de, crypt, mux)
|
||||
s.ConnectAndCopy(c, typeStr, host, en, de, crypt, mux)
|
||||
if mux {
|
||||
utils.FlushConn(conn)
|
||||
goto re
|
||||
|
@ -113,7 +123,20 @@ re:
|
|||
}
|
||||
}
|
||||
|
||||
func Process(c *utils.Conn, typeStr, host string, en, de int, crypt, mux bool) {
|
||||
func (s *TRPClient) session() {
|
||||
t := time.NewTicker(time.Millisecond * 1000)
|
||||
for {
|
||||
select {
|
||||
case <-s.tunnel:
|
||||
case <-t.C:
|
||||
}
|
||||
if s.serverStatus && s.tunnelNum < 5 {
|
||||
go s.dealChan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TRPClient) ConnectAndCopy(c *utils.Conn, typeStr, host string, en, de int, crypt, mux bool) {
|
||||
//与目标建立连接,超时时间为3
|
||||
server, err := net.DialTimeout(typeStr, host, time.Second*3)
|
||||
if err != nil {
|
||||
|
|
|
@ -25,7 +25,7 @@ var (
|
|||
func main() {
|
||||
flag.Parse()
|
||||
server.VerifyKey = *VerifyKey
|
||||
cnf := server.ServerConfig{
|
||||
cnf := &utils.ServerConfig{
|
||||
TcpPort: *httpPort,
|
||||
Mode: *rpMode,
|
||||
Target: *tunnelTarget,
|
||||
|
@ -51,5 +51,5 @@ func main() {
|
|||
}
|
||||
log.Println("服务端启动,监听tcp服务端端口:", *TcpPort)
|
||||
cnf.CompressDecode, cnf.CompressEncode = utils.GetCompressType(cnf.Compress)
|
||||
server.StartNewServer(*TcpPort, &cnf)
|
||||
server.StartNewServer(*TcpPort, cnf)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
2,zl4p3da659qa9rh3,127.0.0.1:58000,测试2,true,,,0,0,
|
||||
1,rfd0tl1anega0d0g,127.0.0.1:53603,测试,true,1,1,1,1,snappy
|
|
|
@ -1,2 +1,2 @@
|
|||
a.o.com,127.0.0.1:8082,7hiixust68kbz33a,,www.baidu.com
|
||||
b.o.com,,7hiixust68kbz33a,,ab
|
||||
a.o.com,127.0.0.1:8080,1,,,测试2
|
||||
b.o.com,127.0.0.1:8082,2,,,测试
|
||||
|
|
|
|
@ -1,5 +1,4 @@
|
|||
8001,tunnelServer,127.0.0.1:88,jq5i7n0sjs1h0jje,aaa,bbb,,1,0,0,0,1
|
||||
0,hostServer,,7n7bxc2bm1fyjfab,ab,b,,1,1,1,0,1
|
||||
0,hostServer,,ts08z6vk5nc72fs8,aaa,bbb,snappy,1,0,1,2,3
|
||||
8002,tunnelServer,127.0.0.1:88,2nxo93wvotb9g75s,,,,1,0,0,0,1
|
||||
8025,socks5Server,,2p3qs71oym3zx52w,,,,1,0,0,0,1
|
||||
9001,tunnelServer,127.0.0.1:8080,,,,1,0,0,0,0,1,1,true,test
|
||||
53,udpServer,114.114.114.114:53,,,,1,0,0,0,0,2,2,true,udp测试
|
||||
8024,socks5Server,,,,,1,0,0,0,0,3,2,true,socks5测试
|
||||
8025,httpProxyServer,,,,,1,0,0,0,0,4,2,true,http测试
|
||||
|
|
|
|
@ -0,0 +1,76 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"github.com/cnlh/easyProxy/bridge"
|
||||
"github.com/cnlh/easyProxy/utils"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//server base struct
|
||||
type server struct {
|
||||
bridge *bridge.Tunnel
|
||||
config *utils.ServerConfig
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *server) GetTunnelAndWriteHost(connType string, cnf *utils.ServerConfig, addr string) (*utils.Conn, error) {
|
||||
var err error
|
||||
link, err := s.bridge.GetTunnel(cnf.ClientId, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = link.WriteHost(connType, addr); err != nil {
|
||||
link.Close()
|
||||
return nil, err
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (s *server) FlowAdd(in, out int64) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.config.Flow == nil {
|
||||
s.config.Flow = new(utils.Flow)
|
||||
}
|
||||
s.config.Flow.ExportFlow += out
|
||||
s.config.Flow.InletFlow += in
|
||||
}
|
||||
|
||||
func (s *server) FlowAddHost(host *utils.HostList, in, out int64) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.config.Flow == nil {
|
||||
s.config.Flow = new(utils.Flow)
|
||||
}
|
||||
host.Flow.ExportFlow += out
|
||||
host.Flow.InletFlow += in
|
||||
}
|
||||
|
||||
//热更新配置
|
||||
func (s *server) ResetConfig() {
|
||||
task, err := CsvDb.GetTask(s.config.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.config.UseClientCnf = task.UseClientCnf
|
||||
if s.config.UseClientCnf {
|
||||
client, err := CsvDb.GetClient(s.config.ClientId)
|
||||
if err == nil {
|
||||
s.config.U = client.Cnf.U
|
||||
s.config.P = client.Cnf.P
|
||||
s.config.Compress = client.Cnf.Compress
|
||||
s.config.Mux = client.Cnf.Mux
|
||||
s.config.Crypt = client.Cnf.Crypt
|
||||
}
|
||||
s.config.CompressDecode, s.config.CompressEncode = utils.GetCompressType(client.Cnf.Compress)
|
||||
} else {
|
||||
if err == nil {
|
||||
s.config.U = task.U
|
||||
s.config.P = task.P
|
||||
s.config.Compress = task.Compress
|
||||
s.config.Mux = task.Mux
|
||||
s.config.Crypt = task.Crypt
|
||||
}
|
||||
s.config.CompressDecode, s.config.CompressEncode = utils.GetCompressType(task.Compress)
|
||||
}
|
||||
}
|
271
server/file.go
271
server/file.go
|
@ -1,271 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/cnlh/easyProxy/utils"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
TcpPort int //服务端与客户端通信端口
|
||||
Mode string //启动方式
|
||||
Target string //目标
|
||||
VerifyKey string //flag
|
||||
U string //socks5验证用户名
|
||||
P string //socks5验证密码
|
||||
Compress string //压缩方式
|
||||
Start int //是否开启
|
||||
IsRun int //是否在运行
|
||||
ClientStatus int //客s户端状态
|
||||
Crypt bool //是否加密
|
||||
Mux bool //是否加密
|
||||
CompressEncode int //加密方式
|
||||
CompressDecode int //解密方式
|
||||
}
|
||||
|
||||
type HostList struct {
|
||||
Vkey string //服务端与客户端通信端口
|
||||
Host string //启动方式
|
||||
Target string //目标
|
||||
HeaderChange string //host修改
|
||||
HostChange string //host修改
|
||||
}
|
||||
|
||||
func NewCsv(runList map[string]interface{}) *Csv {
|
||||
c := new(Csv)
|
||||
c.RunList = runList
|
||||
return c
|
||||
}
|
||||
|
||||
type Csv struct {
|
||||
Tasks []*ServerConfig
|
||||
Path string
|
||||
RunList map[string]interface{}
|
||||
Hosts []*HostList //域名列表
|
||||
}
|
||||
|
||||
func (s *Csv) Init() {
|
||||
s.LoadTaskFromCsv()
|
||||
s.LoadHostFromCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) StoreTasksToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/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),
|
||||
utils.GetStrByBool(task.Crypt),
|
||||
utils.GetStrByBool(task.Mux),
|
||||
strconv.Itoa(task.CompressEncode),
|
||||
strconv.Itoa(task.CompressDecode),
|
||||
}
|
||||
err := writer.Write(record)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) openFile(path string) ([][]string, error) {
|
||||
// 打开文件
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 获取csv的reader
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
// 设置FieldsPerRecord为-1
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
// 读取文件中所有行保存到slice中
|
||||
return reader.ReadAll()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadTaskFromCsv() {
|
||||
path := beego.AppPath + "/conf/tasks.csv"
|
||||
records, err := s.openFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("配置文件打开错误:", path)
|
||||
}
|
||||
var tasks []*ServerConfig
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &ServerConfig{
|
||||
TcpPort: utils.GetIntNoErrByStr(item[0]),
|
||||
Mode: item[1],
|
||||
Target: item[2],
|
||||
VerifyKey: item[3],
|
||||
U: item[4],
|
||||
P: item[5],
|
||||
Compress: item[6],
|
||||
Start: utils.GetIntNoErrByStr(item[7]),
|
||||
Crypt: utils.GetBoolByStr(item[8]),
|
||||
Mux: utils.GetBoolByStr(item[9]),
|
||||
CompressEncode: utils.GetIntNoErrByStr(item[10]),
|
||||
CompressDecode: utils.GetIntNoErrByStr(item[11]),
|
||||
}
|
||||
tasks = append(tasks, post)
|
||||
}
|
||||
s.Tasks = tasks
|
||||
}
|
||||
|
||||
func (s *Csv) NewTask(t *ServerConfig) {
|
||||
s.Tasks = append(s.Tasks, t)
|
||||
s.StoreTasksToCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateTask(t *ServerConfig) 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
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateHost(t *HostList) error {
|
||||
for k, v := range s.Hosts {
|
||||
if v.Host == t.Host {
|
||||
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
|
||||
s.Hosts = append(s.Hosts, t)
|
||||
s.StoreHostToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
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 *ServerConfig, err error) {
|
||||
for _, v = range s.Tasks {
|
||||
if v.VerifyKey == vKey {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("未找到")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Csv) StoreHostToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/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,
|
||||
host.HeaderChange,
|
||||
host.HostChange,
|
||||
}
|
||||
err1 := writer.Write(record)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
}
|
||||
// 确保所有内存数据刷到csv文件
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadHostFromCsv() {
|
||||
path := beego.AppPath + "/conf/hosts.csv"
|
||||
records, err := s.openFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("配置文件打开错误:", path)
|
||||
}
|
||||
var hosts []*HostList
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &HostList{
|
||||
Vkey: item[2],
|
||||
Host: item[0],
|
||||
Target: item[1],
|
||||
HeaderChange: item[3],
|
||||
HostChange: item[4],
|
||||
}
|
||||
hosts = append(hosts, post)
|
||||
}
|
||||
s.Hosts = hosts
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -41,8 +41,8 @@ func ProcessHost(c *utils.Conn, s *TunnelModeServer) error {
|
|||
var (
|
||||
isConn = true
|
||||
link *utils.Conn
|
||||
cnf *ServerConfig
|
||||
host *HostList
|
||||
client *utils.Client
|
||||
host *utils.HostList
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
for {
|
||||
|
@ -52,48 +52,56 @@ func ProcessHost(c *utils.Conn, s *TunnelModeServer) error {
|
|||
}
|
||||
//首次获取conn
|
||||
if isConn {
|
||||
isConn = false
|
||||
if host, cnf, err = GetKeyByHost(r.Host); err != nil {
|
||||
if host, client, err = GetKeyByHost(r.Host); err != nil {
|
||||
log.Printf("the host %s is not found !", r.Host)
|
||||
break
|
||||
}
|
||||
|
||||
if err = s.auth(r, c, cnf.U, cnf.P); err != nil {
|
||||
client.Cnf.ClientId = host.ClientId
|
||||
client.Cnf.CompressDecode, client.Cnf.CompressEncode = utils.GetCompressType(client.Cnf.Compress)
|
||||
if err = s.auth(r, c, client.Cnf.U, client.Cnf.P); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, cnf, host.Target); err != nil {
|
||||
if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, client.Cnf, host.Target); err != nil {
|
||||
log.Println("get bridge tunnel error: ", err)
|
||||
break
|
||||
}
|
||||
|
||||
if flag, err := link.ReadFlag(); err != nil || flag == utils.CONN_ERROR {
|
||||
log.Printf("the host %s connection to %s error", r.Host, host.Target)
|
||||
break
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
utils.Relay(c.Conn, link.Conn, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
out, _ := utils.Relay(c.Conn, link.Conn, client.Cnf.CompressDecode, client.Cnf.Crypt, client.Cnf.Mux)
|
||||
wg.Done()
|
||||
s.FlowAddHost(host, 0, out)
|
||||
}()
|
||||
}
|
||||
isConn = false
|
||||
}
|
||||
utils.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String())
|
||||
b, err := httputil.DumpRequest(r, true)
|
||||
s.FlowAddHost(host, int64(len(b)), 0)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if _, err := link.WriteTo(b, cnf.CompressEncode, cnf.Crypt); err != nil {
|
||||
if _, err := link.WriteTo(b, client.Cnf.CompressEncode, client.Cnf.Crypt); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
if cnf != nil && cnf.Mux && link != nil {
|
||||
link.WriteTo([]byte(utils.IO_EOF), cnf.CompressEncode, cnf.Crypt)
|
||||
s.bridge.ReturnTunnel(link, getverifyval(cnf.VerifyKey))
|
||||
if client != nil && client.Cnf != nil && client.Cnf.Mux && link != nil {
|
||||
link.WriteTo([]byte(utils.IO_EOF), client.Cnf.CompressEncode, client.Cnf.Crypt)
|
||||
s.bridge.ReturnTunnel(link, client.Id)
|
||||
} else if link != nil {
|
||||
link.Close()
|
||||
}
|
||||
|
||||
if isConn {
|
||||
s.writeConnFail(c.Conn)
|
||||
}
|
||||
c.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
|
187
server/server.go
187
server/server.go
|
@ -11,35 +11,43 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
type RunServer struct {
|
||||
flag int //标志
|
||||
ExportFlow int64 //出口流量
|
||||
InletFlow int64 //入口流量
|
||||
service interface{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
Bridge *bridge.Tunnel
|
||||
RunList map[string]interface{} //运行中的任务
|
||||
CsvDb *Csv
|
||||
RunList map[int]interface{} //运行中的任务
|
||||
CsvDb = utils.GetCsvDb()
|
||||
VerifyKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
RunList = make(map[string]interface{})
|
||||
RunList = make(map[int]interface{})
|
||||
}
|
||||
|
||||
//从csv文件中恢复任务
|
||||
func InitFromCsv() {
|
||||
for _, v := range CsvDb.Tasks {
|
||||
if v.Start == 1 {
|
||||
log.Println("启动模式:", v.Mode, "监听端口:", v.TcpPort, "客户端令牌:", v.VerifyKey)
|
||||
log.Println("启动模式:", v.Mode, "监听端口:", v.TcpPort)
|
||||
AddTask(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//start a new server
|
||||
func StartNewServer(bridgePort int, cnf *ServerConfig) {
|
||||
func StartNewServer(bridgePort int, cnf *utils.ServerConfig) {
|
||||
Bridge = bridge.NewTunnel(bridgePort, RunList)
|
||||
if err := Bridge.StartTunnel(); err != nil {
|
||||
log.Fatalln("服务端开启失败", err)
|
||||
}
|
||||
if svr := NewMode(Bridge, cnf); svr != nil {
|
||||
RunList[cnf.VerifyKey] = svr
|
||||
RunList[cnf.Id] = svr
|
||||
err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
|
||||
if err.Interface() != nil {
|
||||
log.Println(err)
|
||||
|
@ -50,7 +58,8 @@ func StartNewServer(bridgePort int, cnf *ServerConfig) {
|
|||
}
|
||||
|
||||
//new a server by mode name
|
||||
func NewMode(Bridge *bridge.Tunnel, config *ServerConfig) interface{} {
|
||||
func NewMode(Bridge *bridge.Tunnel, c *utils.ServerConfig) interface{} {
|
||||
config := utils.DeepCopyConfig(c)
|
||||
switch config.Mode {
|
||||
case "tunnelServer":
|
||||
return NewTunnelModeServer(ProcessTunnel, Bridge, config)
|
||||
|
@ -61,14 +70,12 @@ func NewMode(Bridge *bridge.Tunnel, config *ServerConfig) interface{} {
|
|||
case "udpServer":
|
||||
return NewUdpModeServer(Bridge, config)
|
||||
case "webServer":
|
||||
InitCsvDb()
|
||||
InitFromCsv()
|
||||
p, _ := beego.AppConfig.Int("hostPort")
|
||||
t := &ServerConfig{
|
||||
t := &utils.ServerConfig{
|
||||
TcpPort: p,
|
||||
Mode: "httpHostServer",
|
||||
Target: "",
|
||||
VerifyKey: "",
|
||||
U: "",
|
||||
P: "",
|
||||
Compress: "",
|
||||
|
@ -87,15 +94,10 @@ func NewMode(Bridge *bridge.Tunnel, config *ServerConfig) interface{} {
|
|||
}
|
||||
|
||||
//stop server
|
||||
func StopServer(cFlag string) error {
|
||||
if v, ok := RunList[cFlag]; ok {
|
||||
func StopServer(id int) error {
|
||||
if v, ok := RunList[id]; ok {
|
||||
reflect.ValueOf(v).MethodByName("Close").Call(nil)
|
||||
delete(RunList, cFlag)
|
||||
if VerifyKey == "" { //多客户端模式关闭相关隧道
|
||||
Bridge.DelClientSignal(cFlag)
|
||||
Bridge.DelClientTunnel(cFlag)
|
||||
}
|
||||
if t, err := CsvDb.GetTask(cFlag); err != nil {
|
||||
if t, err := CsvDb.GetTask(id); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.Start = 0
|
||||
|
@ -107,15 +109,14 @@ func StopServer(cFlag string) error {
|
|||
}
|
||||
|
||||
//add task
|
||||
func AddTask(t *ServerConfig) error {
|
||||
t.CompressDecode, t.CompressEncode = utils.GetCompressType(t.Compress)
|
||||
func AddTask(t *utils.ServerConfig) error {
|
||||
if svr := NewMode(Bridge, t); svr != nil {
|
||||
RunList[t.VerifyKey] = svr
|
||||
RunList[t.Id] = 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)
|
||||
log.Println("客户端", t.Id, "启动失败,错误:", err)
|
||||
delete(RunList, t.Id)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
|
@ -125,8 +126,8 @@ func AddTask(t *ServerConfig) error {
|
|||
}
|
||||
|
||||
//start task
|
||||
func StartTask(vKey string) error {
|
||||
if t, err := CsvDb.GetTask(vKey); err != nil {
|
||||
func StartTask(id int) error {
|
||||
if t, err := CsvDb.GetTask(id); err != nil {
|
||||
return err
|
||||
} else {
|
||||
AddTask(t)
|
||||
|
@ -137,35 +138,20 @@ func StartTask(vKey string) error {
|
|||
}
|
||||
|
||||
//delete task
|
||||
func DelTask(vKey string) error {
|
||||
if err := StopServer(vKey); err != nil {
|
||||
func DelTask(id int) error {
|
||||
if err := StopServer(id); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range CsvDb.Hosts {
|
||||
if v.Vkey == vKey {
|
||||
CsvDb.DelHost(v.Host)
|
||||
}
|
||||
}
|
||||
return CsvDb.DelTask(vKey)
|
||||
}
|
||||
|
||||
//init csv from file
|
||||
func InitCsvDb() *Csv {
|
||||
var once sync.Once
|
||||
once.Do(func() {
|
||||
CsvDb = NewCsv(RunList)
|
||||
CsvDb.Init()
|
||||
})
|
||||
return CsvDb
|
||||
return CsvDb.DelTask(id)
|
||||
}
|
||||
|
||||
//get key by host from x
|
||||
func GetKeyByHost(host string) (h *HostList, t *ServerConfig, err error) {
|
||||
func GetKeyByHost(host string) (h *utils.HostList, t *utils.Client, err error) {
|
||||
for _, v := range CsvDb.Hosts {
|
||||
s := strings.Split(host, ":")
|
||||
if s[0] == v.Host {
|
||||
h = v
|
||||
t, err = CsvDb.GetTask(v.Vkey)
|
||||
t, err = CsvDb.GetClient(v.ClientId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -174,22 +160,32 @@ func GetKeyByHost(host string) (h *HostList, t *ServerConfig, err error) {
|
|||
}
|
||||
|
||||
//get task list by page num
|
||||
func GetServerConfig(start, length int, typeVal string) ([]*ServerConfig, int) {
|
||||
list := make([]*ServerConfig, 0)
|
||||
func GetServerConfig(start, length int, typeVal string, clientId int) ([]*utils.ServerConfig, int) {
|
||||
list := make([]*utils.ServerConfig, 0)
|
||||
var cnt int
|
||||
for _, v := range CsvDb.Tasks {
|
||||
if v.Mode != typeVal {
|
||||
if (typeVal != "" && v.Mode != typeVal) || (typeVal == "" && clientId != v.ClientId) {
|
||||
continue
|
||||
}
|
||||
if v.UseClientCnf {
|
||||
v = utils.DeepCopyConfig(v)
|
||||
if c, err := CsvDb.GetClient(v.ClientId); err == nil {
|
||||
v.Compress = c.Cnf.Compress
|
||||
v.Mux = c.Cnf.Mux
|
||||
v.Crypt = c.Cnf.Crypt
|
||||
v.U = c.Cnf.U
|
||||
v.P = c.Cnf.P
|
||||
}
|
||||
}
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length > 0 {
|
||||
if _, ok := RunList[v.VerifyKey]; ok {
|
||||
if _, ok := RunList[v.Id]; ok {
|
||||
v.IsRun = 1
|
||||
} else {
|
||||
v.IsRun = 0
|
||||
}
|
||||
if s, ok := Bridge.SignalList[getverifyval(v.VerifyKey)]; ok {
|
||||
if s, ok := Bridge.SignalList[v.ClientId]; ok {
|
||||
if s.Len() > 0 {
|
||||
v.ClientStatus = 1
|
||||
} else {
|
||||
|
@ -201,16 +197,91 @@ func GetServerConfig(start, length int, typeVal string) ([]*ServerConfig, int) {
|
|||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return list, cnt
|
||||
}
|
||||
|
||||
//get verify value
|
||||
//when mode is webServer and vKey is not none
|
||||
func getverifyval(vkey string) string {
|
||||
if VerifyKey != "" {
|
||||
return utils.Md5(VerifyKey)
|
||||
}
|
||||
return utils.Md5(vkey)
|
||||
//获取客户端列表
|
||||
func GetClientList(start, length int) (list []*utils.Client, cnt int) {
|
||||
list, cnt = CsvDb.GetClientList(start, length)
|
||||
dealClientData(list)
|
||||
return
|
||||
}
|
||||
|
||||
func dealClientData(list []*utils.Client) {
|
||||
for _, v := range list {
|
||||
if _, ok := Bridge.SignalList[v.Id]; ok {
|
||||
v.IsConnect = true
|
||||
} else {
|
||||
v.IsConnect = false
|
||||
}
|
||||
v.Flow.InletFlow = 0
|
||||
v.Flow.ExportFlow = 0
|
||||
for _, h := range CsvDb.Hosts {
|
||||
if h.ClientId == v.Id {
|
||||
v.Flow.InletFlow += h.Flow.InletFlow
|
||||
v.Flow.ExportFlow += h.Flow.ExportFlow
|
||||
}
|
||||
}
|
||||
for _, t := range CsvDb.Tasks {
|
||||
if t.ClientId == v.Id {
|
||||
v.Flow.InletFlow += t.Flow.InletFlow
|
||||
v.Flow.ExportFlow += t.Flow.ExportFlow
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//根据客户端id删除其所属的所有隧道和域名
|
||||
func DelTunnelAndHostByClientId(clientId int) {
|
||||
for _, v := range CsvDb.Tasks {
|
||||
if v.ClientId == clientId {
|
||||
DelTask(v.Id)
|
||||
}
|
||||
}
|
||||
for _, v := range CsvDb.Hosts {
|
||||
if v.ClientId == clientId {
|
||||
CsvDb.DelHost(v.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//关闭客户端连接
|
||||
func DelClientConnect(clientId int) {
|
||||
Bridge.DelClientTunnel(clientId)
|
||||
Bridge.DelClientSignal(clientId)
|
||||
}
|
||||
|
||||
func GetDashboardData() map[string]int {
|
||||
data := make(map[string]int)
|
||||
data["hostCount"] = len(CsvDb.Hosts)
|
||||
data["clientCount"] = len(CsvDb.Clients)
|
||||
list := CsvDb.Clients
|
||||
dealClientData(list)
|
||||
c := 0
|
||||
var in, out int64
|
||||
for _, v := range list {
|
||||
if v.IsConnect {
|
||||
c += 1
|
||||
}
|
||||
in += v.Flow.InletFlow
|
||||
out += v.Flow.ExportFlow
|
||||
}
|
||||
data["clientOnlineCount"] = c
|
||||
data["inletFlowCount"] = int(in)
|
||||
data["exportFlowCount"] = int(out)
|
||||
for _, v := range CsvDb.Tasks {
|
||||
switch v.Mode {
|
||||
case "tunnelServer":
|
||||
data["tunnelServerCount"] += 1
|
||||
case "socks5Server":
|
||||
data["socks5ServerCount"] += 1
|
||||
case "httpProxyServer":
|
||||
data["httpProxyServerCount"] += 1
|
||||
case "udpServer":
|
||||
data["udpServerCount"] += 1
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
|
|
@ -160,13 +160,14 @@ func (s *Sock5ModeServer) handleConnect(c net.Conn) {
|
|||
proxyConn, err := s.doConnect(c, connectMethod)
|
||||
defer func() {
|
||||
if s.config.Mux && proxyConn != nil {
|
||||
s.bridge.ReturnTunnel(proxyConn, getverifyval(s.config.VerifyKey))
|
||||
s.bridge.ReturnTunnel(proxyConn, s.config.ClientId)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
} else {
|
||||
utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
out, in := utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
s.FlowAdd(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,13 +198,14 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
|
|||
proxyConn, err := s.doConnect(c, associateMethod)
|
||||
defer func() {
|
||||
if s.config.Mux && proxyConn != nil {
|
||||
s.bridge.ReturnTunnel(proxyConn, getverifyval(s.config.VerifyKey))
|
||||
s.bridge.ReturnTunnel(proxyConn, s.config.ClientId)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
} else {
|
||||
utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
out, in := utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
s.FlowAdd(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,6 +297,7 @@ func (s *Sock5ModeServer) Start() error {
|
|||
}
|
||||
log.Fatal("accept error: ", err)
|
||||
}
|
||||
s.ResetConfig()
|
||||
go s.handleConn(conn)
|
||||
}
|
||||
return nil
|
||||
|
@ -306,7 +309,7 @@ func (s *Sock5ModeServer) Close() error {
|
|||
}
|
||||
|
||||
//new
|
||||
func NewSock5ModeServer(bridge *bridge.Tunnel, cnf *ServerConfig) *Sock5ModeServer {
|
||||
func NewSock5ModeServer(bridge *bridge.Tunnel, cnf *utils.ServerConfig) *Sock5ModeServer {
|
||||
s := new(Sock5ModeServer)
|
||||
s.bridge = bridge
|
||||
s.config = cnf
|
||||
|
|
|
@ -12,33 +12,15 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
//server base struct
|
||||
type server struct {
|
||||
bridge *bridge.Tunnel
|
||||
config *ServerConfig
|
||||
}
|
||||
|
||||
func (s *server) GetTunnelAndWriteHost(connType string, cnf *ServerConfig, addr string) (*utils.Conn, error) {
|
||||
var err error
|
||||
link, err := s.bridge.GetTunnel(getverifyval(cnf.VerifyKey), cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = link.WriteHost(connType, addr); err != nil {
|
||||
link.Close()
|
||||
return nil, err
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
type TunnelModeServer struct {
|
||||
server
|
||||
process process
|
||||
listener *net.TCPListener
|
||||
errorContent []byte
|
||||
process process
|
||||
listener *net.TCPListener
|
||||
}
|
||||
|
||||
//tcp|http|host
|
||||
func NewTunnelModeServer(process process, bridge *bridge.Tunnel, cnf *ServerConfig) *TunnelModeServer {
|
||||
func NewTunnelModeServer(process process, bridge *bridge.Tunnel, cnf *utils.ServerConfig) *TunnelModeServer {
|
||||
s := new(TunnelModeServer)
|
||||
s.bridge = bridge
|
||||
s.process = process
|
||||
|
@ -49,6 +31,9 @@ func NewTunnelModeServer(process process, bridge *bridge.Tunnel, cnf *ServerConf
|
|||
//开始
|
||||
func (s *TunnelModeServer) Start() error {
|
||||
var err error
|
||||
if s.errorContent, err = utils.ReadAllFromFile(beego.AppPath + "/web/static/page/error.html"); err != nil {
|
||||
s.errorContent = []byte("easyProxy 404")
|
||||
}
|
||||
s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.config.TcpPort, ""})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -62,6 +47,7 @@ func (s *TunnelModeServer) Start() error {
|
|||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
s.ResetConfig()
|
||||
go s.process(utils.NewConn(conn), s)
|
||||
}
|
||||
return nil
|
||||
|
@ -70,20 +56,25 @@ func (s *TunnelModeServer) Start() error {
|
|||
//权限认证
|
||||
func (s *TunnelModeServer) auth(r *http.Request, c *utils.Conn, u, p string) error {
|
||||
if u != "" && p != "" && !utils.CheckAuth(r, u, p) {
|
||||
c.Write([]byte(utils.Unauthorized_BYTES))
|
||||
c.Write([]byte(utils.UnauthorizedBytes))
|
||||
c.Close()
|
||||
return errors.New("401 Unauthorized")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TunnelModeServer) writeConnFail(c net.Conn) {
|
||||
c.Write([]byte(utils.ConnectionFailBytes))
|
||||
c.Write(s.errorContent)
|
||||
}
|
||||
|
||||
//与客户端建立通道
|
||||
func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *ServerConfig, addr string, method string, rb []byte) error {
|
||||
func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *utils.ServerConfig, addr string, method string, rb []byte) error {
|
||||
var link *utils.Conn
|
||||
var err error
|
||||
defer func() {
|
||||
if cnf.Mux && link != nil {
|
||||
s.bridge.ReturnTunnel(link, getverifyval(cnf.VerifyKey))
|
||||
s.bridge.ReturnTunnel(link, cnf.ClientId)
|
||||
}
|
||||
}()
|
||||
if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, cnf, addr); err != nil {
|
||||
|
@ -97,7 +88,8 @@ func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *ServerConfig, addr str
|
|||
} else if rb != nil {
|
||||
link.WriteTo(rb, cnf.CompressEncode, cnf.Crypt)
|
||||
}
|
||||
utils.ReplayWaitGroup(link.Conn, c.Conn, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
out, in := utils.ReplayWaitGroup(link.Conn, c.Conn, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
s.FlowAdd(in, out)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -139,7 +131,7 @@ func (s *HostServer) Start() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewHostServer(cnf *ServerConfig) *HostServer {
|
||||
func NewHostServer(cnf *utils.ServerConfig) *HostServer {
|
||||
s := new(HostServer)
|
||||
s.config = cnf
|
||||
return s
|
||||
|
|
|
@ -10,13 +10,12 @@ import (
|
|||
)
|
||||
|
||||
type UdpModeServer struct {
|
||||
bridge *bridge.Tunnel
|
||||
server
|
||||
listener *net.UDPConn
|
||||
udpMap map[string]*utils.Conn
|
||||
config *ServerConfig
|
||||
}
|
||||
|
||||
func NewUdpModeServer(bridge *bridge.Tunnel, cnf *ServerConfig) *UdpModeServer {
|
||||
func NewUdpModeServer(bridge *bridge.Tunnel, cnf *utils.ServerConfig) *UdpModeServer {
|
||||
s := new(UdpModeServer)
|
||||
s.bridge = bridge
|
||||
s.udpMap = make(map[string]*utils.Conn)
|
||||
|
@ -40,6 +39,7 @@ func (s *UdpModeServer) Start() error {
|
|||
}
|
||||
continue
|
||||
}
|
||||
s.ResetConfig()
|
||||
go s.process(addr, data[:n])
|
||||
}
|
||||
return nil
|
||||
|
@ -47,7 +47,7 @@ func (s *UdpModeServer) Start() error {
|
|||
|
||||
//TODO:效率问题有待解决
|
||||
func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
|
||||
conn, err := s.bridge.GetTunnel(getverifyval(s.config.VerifyKey), s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
conn, err := s.bridge.GetTunnel(s.config.ClientId, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
@ -60,21 +60,22 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
|
|||
defer func() {
|
||||
if conn != nil && s.config.Mux {
|
||||
conn.WriteTo([]byte(utils.IO_EOF), s.config.CompressEncode, s.config.Crypt)
|
||||
s.bridge.ReturnTunnel(conn, getverifyval(s.config.VerifyKey))
|
||||
s.bridge.ReturnTunnel(conn, s.config.ClientId)
|
||||
} else {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
if flag == utils.CONN_SUCCESS {
|
||||
conn.WriteTo(data, s.config.CompressEncode, s.config.Crypt)
|
||||
buf := make([]byte, 1024)
|
||||
//conn.conn.SetReadDeadline(time.Now().Add(time.Duration(time.Second * 3)))
|
||||
n, err := conn.ReadFrom(buf, s.config.CompressDecode, s.config.Crypt)
|
||||
in, _ := conn.WriteTo(data, s.config.CompressEncode, s.config.Crypt)
|
||||
buf := utils.BufPoolUdp.Get().([]byte)
|
||||
out, err := conn.ReadFrom(buf, s.config.CompressDecode, s.config.Crypt)
|
||||
if err != nil || err == io.EOF {
|
||||
log.Println("revieve error:", err)
|
||||
return
|
||||
}
|
||||
s.listener.WriteToUDP(buf[:n], addr)
|
||||
s.listener.WriteToUDP(buf[:out], addr)
|
||||
s.FlowAdd(int64(in), int64(out))
|
||||
utils.BufPoolUdp.Put(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,14 +107,14 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
|
|||
|
||||
//snappy压缩读 包含解密
|
||||
func (s *SnappyConn) Read(b []byte) (n int, err error) {
|
||||
buf := bufPool.Get().([]byte)
|
||||
defer func() {
|
||||
if err == nil && n == len(IO_EOF) && string(b[:n]) == IO_EOF {
|
||||
err = io.EOF
|
||||
n = 0
|
||||
}
|
||||
bufPool.Put(buf)
|
||||
}()
|
||||
buf := bufPool.Get().([]byte)
|
||||
defer bufPool.Put(buf)
|
||||
if n, err = s.r.Read(buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -153,8 +153,8 @@ func (s *Conn) ReadLen(cLen int) ([]byte, error) {
|
|||
buf = bufPoolSmall.Get().([]byte)[:cLen]
|
||||
defer bufPoolSmall.Put(buf)
|
||||
} else {
|
||||
buf = bufPool.Get().([]byte)[:cLen]
|
||||
defer bufPool.Put(buf)
|
||||
buf = bufPoolMax.Get().([]byte)[:cLen]
|
||||
defer bufPoolMax.Put(buf)
|
||||
}
|
||||
if n, err := io.ReadFull(s, buf); err != nil || n != cLen {
|
||||
return buf, errors.New("读取指定长度错误" + err.Error())
|
||||
|
|
|
@ -0,0 +1,487 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"easyProxy/utils"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"github.com/astaxie/beego"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
CsvDb *Csv
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type Flow struct {
|
||||
ExportFlow int64 //出口流量
|
||||
InletFlow int64 //入口流量
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Cnf *ServerConfig
|
||||
Id int //id
|
||||
VerifyKey string //验证密钥
|
||||
Addr string //客户端ip地址
|
||||
Remark string //备注
|
||||
Status bool //是否开启
|
||||
IsConnect bool //是否连接
|
||||
Flow *Flow
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
TcpPort int //服务端与客户端通信端口
|
||||
VerifyKey string
|
||||
Mode string //启动方式
|
||||
Target string //目标
|
||||
U string //socks5验证用户名
|
||||
P string //socks5验证密码
|
||||
Compress string //压缩方式
|
||||
Start int //是否开启
|
||||
IsRun int //是否在运行
|
||||
ClientStatus int //客s户端状态
|
||||
Crypt bool //是否加密
|
||||
Mux bool //是否加密
|
||||
CompressEncode int //加密方式
|
||||
CompressDecode int //解密方式
|
||||
Id int //Id
|
||||
ClientId int //所属客户端id
|
||||
UseClientCnf bool //是否继承客户端配置
|
||||
Flow *Flow
|
||||
Remark string //备注
|
||||
}
|
||||
|
||||
type HostList struct {
|
||||
ClientId int //服务端与客户端通信端口
|
||||
Host string //启动方式
|
||||
Target string //目标
|
||||
HeaderChange string //host修改
|
||||
HostChange string //host修改
|
||||
Flow *Flow
|
||||
Remark string //备注
|
||||
}
|
||||
|
||||
func NewCsv() *Csv {
|
||||
c := new(Csv)
|
||||
return c
|
||||
}
|
||||
|
||||
type Csv struct {
|
||||
Tasks []*ServerConfig
|
||||
Path string
|
||||
Hosts []*HostList //域名列表
|
||||
Clients []*Client //客户端
|
||||
ClientIncreaseId int //客户端id
|
||||
TaskIncreaseId int //任务自增ID
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *Csv) Init() {
|
||||
s.LoadTaskFromCsv()
|
||||
s.LoadHostFromCsv()
|
||||
s.LoadClientFromCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) StoreTasksToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/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.U,
|
||||
task.P,
|
||||
task.Compress,
|
||||
strconv.Itoa(task.Start),
|
||||
GetStrByBool(task.Crypt),
|
||||
GetStrByBool(task.Mux),
|
||||
strconv.Itoa(task.CompressEncode),
|
||||
strconv.Itoa(task.CompressDecode),
|
||||
strconv.Itoa(task.Id),
|
||||
strconv.Itoa(task.ClientId),
|
||||
strconv.FormatBool(task.UseClientCnf),
|
||||
task.Remark,
|
||||
}
|
||||
err := writer.Write(record)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) openFile(path string) ([][]string, error) {
|
||||
// 打开文件
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 获取csv的reader
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
// 设置FieldsPerRecord为-1
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
// 读取文件中所有行保存到slice中
|
||||
return reader.ReadAll()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadTaskFromCsv() {
|
||||
path := beego.AppPath + "/conf/tasks.csv"
|
||||
records, err := s.openFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("配置文件打开错误:", path)
|
||||
}
|
||||
var tasks []*ServerConfig
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &ServerConfig{
|
||||
TcpPort: GetIntNoErrByStr(item[0]),
|
||||
Mode: item[1],
|
||||
Target: item[2],
|
||||
U: item[3],
|
||||
P: item[4],
|
||||
Compress: item[5],
|
||||
Start: GetIntNoErrByStr(item[6]),
|
||||
Crypt: GetBoolByStr(item[7]),
|
||||
Mux: GetBoolByStr(item[8]),
|
||||
CompressEncode: GetIntNoErrByStr(item[9]),
|
||||
CompressDecode: GetIntNoErrByStr(item[10]),
|
||||
Id: GetIntNoErrByStr(item[11]),
|
||||
ClientId: GetIntNoErrByStr(item[12]),
|
||||
UseClientCnf: GetBoolByStr(item[13]),
|
||||
Remark: item[14],
|
||||
}
|
||||
post.Flow = new(Flow)
|
||||
tasks = append(tasks, post)
|
||||
if post.Id > s.TaskIncreaseId {
|
||||
s.TaskIncreaseId = post.Id
|
||||
}
|
||||
}
|
||||
s.Tasks = tasks
|
||||
}
|
||||
|
||||
func (s *Csv) GetTaskId() int {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.TaskIncreaseId++
|
||||
return s.TaskIncreaseId
|
||||
}
|
||||
|
||||
func (s *Csv) GetIdByVerifyKey(vKey string, addr string) (int, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for _, v := range s.Clients {
|
||||
if utils.Getverifyval(v.VerifyKey) == vKey && v.Status {
|
||||
v.Addr = addr
|
||||
return v.Id, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("not found")
|
||||
}
|
||||
|
||||
func (s *Csv) NewTask(t *ServerConfig) {
|
||||
t.Flow = new(Flow)
|
||||
s.Tasks = append(s.Tasks, t)
|
||||
s.StoreTasksToCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateTask(t *ServerConfig) error {
|
||||
for k, v := range s.Tasks {
|
||||
if v.Id == t.Id {
|
||||
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
|
||||
s.Tasks = append(s.Tasks, t)
|
||||
s.StoreTasksToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) DelTask(id int) error {
|
||||
for k, v := range s.Tasks {
|
||||
if v.Id == id {
|
||||
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
|
||||
s.StoreTasksToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) GetTask(id int) (v *ServerConfig, err error) {
|
||||
for _, v = range s.Tasks {
|
||||
if v.Id == id {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("未找到")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Csv) StoreHostToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/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,
|
||||
strconv.Itoa(host.ClientId),
|
||||
host.HeaderChange,
|
||||
host.HostChange,
|
||||
host.Remark,
|
||||
}
|
||||
err1 := writer.Write(record)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
}
|
||||
// 确保所有内存数据刷到csv文件
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadClientFromCsv() {
|
||||
path := beego.AppPath + "/conf/clients.csv"
|
||||
records, err := s.openFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("配置文件打开错误:", path)
|
||||
}
|
||||
var clients []*Client
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &Client{
|
||||
Id: GetIntNoErrByStr(item[0]),
|
||||
VerifyKey: item[1],
|
||||
Addr: item[2],
|
||||
Remark: item[3],
|
||||
Status: GetBoolByStr(item[4]),
|
||||
Cnf: &ServerConfig{
|
||||
U: item[5],
|
||||
P: item[6],
|
||||
Crypt: GetBoolByStr(item[7]),
|
||||
Mux: GetBoolByStr(item[8]),
|
||||
Compress: item[9],
|
||||
},
|
||||
}
|
||||
if post.Id > s.ClientIncreaseId {
|
||||
s.ClientIncreaseId = post.Id
|
||||
}
|
||||
post.Flow = new(Flow)
|
||||
clients = append(clients, post)
|
||||
}
|
||||
s.Clients = clients
|
||||
}
|
||||
|
||||
func (s *Csv) LoadHostFromCsv() {
|
||||
path := beego.AppPath + "/conf/hosts.csv"
|
||||
records, err := s.openFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("配置文件打开错误:", path)
|
||||
}
|
||||
var hosts []*HostList
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &HostList{
|
||||
ClientId: GetIntNoErrByStr(item[2]),
|
||||
Host: item[0],
|
||||
Target: item[1],
|
||||
HeaderChange: item[3],
|
||||
HostChange: item[4],
|
||||
Remark: item[5],
|
||||
}
|
||||
post.Flow = new(Flow)
|
||||
hosts = append(hosts, post)
|
||||
}
|
||||
s.Hosts = hosts
|
||||
}
|
||||
|
||||
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) {
|
||||
t.Flow = new(Flow)
|
||||
s.Hosts = append(s.Hosts, t)
|
||||
s.StoreHostToCsv()
|
||||
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateHost(t *HostList) error {
|
||||
for k, v := range s.Hosts {
|
||||
if v.Host == t.Host {
|
||||
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
|
||||
s.Hosts = append(s.Hosts, t)
|
||||
s.StoreHostToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) GetHostList(start, length int, id int) ([]*HostList, int) {
|
||||
list := make([]*HostList, 0)
|
||||
var cnt int
|
||||
for _, v := range s.Hosts {
|
||||
if id == 0 || v.ClientId == id {
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length > 0 {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, cnt
|
||||
}
|
||||
|
||||
func (s *Csv) DelClient(id int) error {
|
||||
for k, v := range s.Clients {
|
||||
if v.Id == id {
|
||||
s.Clients = append(s.Clients[:k], s.Clients[k+1:]...)
|
||||
s.StoreClientsToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) NewClient(c *Client) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
c.Flow = new(Flow)
|
||||
s.Clients = append(s.Clients, c)
|
||||
s.StoreClientsToCsv()
|
||||
}
|
||||
|
||||
func (s *Csv) GetClientId() int {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.ClientIncreaseId++
|
||||
return s.ClientIncreaseId
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateClient(t *Client) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for k, v := range s.Clients {
|
||||
if v.Id == t.Id {
|
||||
s.Clients = append(s.Clients[:k], s.Clients[k+1:]...)
|
||||
s.Clients = append(s.Clients, t)
|
||||
s.StoreClientsToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) GetClientList(start, length int) ([]*Client, int) {
|
||||
list := make([]*Client, 0)
|
||||
var cnt int
|
||||
for _, v := range s.Clients {
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length > 0 {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, cnt
|
||||
}
|
||||
|
||||
func (s *Csv) GetClient(id int) (v *Client, err error) {
|
||||
for _, v = range s.Clients {
|
||||
if v.Id == id {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("未找到")
|
||||
return
|
||||
}
|
||||
func (s *Csv) StoreClientsToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/clients.csv")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
defer csvFile.Close()
|
||||
writer := csv.NewWriter(csvFile)
|
||||
for _, client := range s.Clients {
|
||||
record := []string{
|
||||
strconv.Itoa(client.Id),
|
||||
client.VerifyKey,
|
||||
client.Addr,
|
||||
client.Remark,
|
||||
strconv.FormatBool(client.Status),
|
||||
client.Cnf.U,
|
||||
client.Cnf.P,
|
||||
utils.GetStrByBool(client.Cnf.Crypt),
|
||||
utils.GetStrByBool(client.Cnf.Mux),
|
||||
client.Cnf.Compress,
|
||||
}
|
||||
err := writer.Write(record)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
//init csv from file
|
||||
func GetCsvDb() *Csv {
|
||||
once.Do(func() {
|
||||
CsvDb = NewCsv()
|
||||
CsvDb.Init()
|
||||
})
|
||||
return CsvDb
|
||||
}
|
||||
|
||||
//深拷贝serverConfig
|
||||
func DeepCopyConfig(c *ServerConfig) *ServerConfig {
|
||||
return &ServerConfig{
|
||||
TcpPort: c.TcpPort,
|
||||
VerifyKey: c.VerifyKey,
|
||||
Mode: c.Mode,
|
||||
Target: c.Target,
|
||||
U: c.U,
|
||||
P: c.P,
|
||||
Compress: c.Compress,
|
||||
Start: c.Start,
|
||||
IsRun: c.IsRun,
|
||||
ClientStatus: c.ClientStatus,
|
||||
Crypt: c.Crypt,
|
||||
Mux: c.Mux,
|
||||
CompressEncode: c.CompressEncode,
|
||||
CompressDecode: c.CompressDecode,
|
||||
Id: c.Id,
|
||||
ClientId: c.ClientId,
|
||||
UseClientCnf: c.UseClientCnf,
|
||||
Flow: c.Flow,
|
||||
Remark: c.Remark,
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@ package utils
|
|||
import "sync"
|
||||
|
||||
const poolSize = 64 * 1024
|
||||
const poolSizeSmall = 10
|
||||
const poolSizeSmall = 100
|
||||
const poolSizeUdp = 1472
|
||||
const poolSizeCopy = 32 * 1024
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
|
@ -11,6 +12,16 @@ var bufPool = sync.Pool{
|
|||
return make([]byte, poolSize)
|
||||
},
|
||||
}
|
||||
var BufPoolUdp = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, poolSizeUdp)
|
||||
},
|
||||
}
|
||||
var bufPoolMax = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, poolSize)
|
||||
},
|
||||
}
|
||||
var bufPoolSmall = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, poolSizeSmall)
|
||||
|
|
|
@ -3,9 +3,11 @@ package utils
|
|||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -18,48 +20,52 @@ const (
|
|||
COMPRESS_NONE_DECODE
|
||||
COMPRESS_SNAPY_ENCODE
|
||||
COMPRESS_SNAPY_DECODE
|
||||
VERIFY_EER = "vkey"
|
||||
WORK_MAIN = "main"
|
||||
WORK_CHAN = "chan"
|
||||
RES_SIGN = "sign"
|
||||
RES_MSG = "msg0"
|
||||
CONN_SUCCESS = "sucs"
|
||||
CONN_ERROR = "fail"
|
||||
TEST_FLAG = "tst"
|
||||
CONN_TCP = "tcp"
|
||||
CONN_UDP = "udp"
|
||||
Unauthorized_BYTES = `HTTP/1.1 401 Unauthorized
|
||||
VERIFY_EER = "vkey"
|
||||
WORK_MAIN = "main"
|
||||
WORK_CHAN = "chan"
|
||||
RES_SIGN = "sign"
|
||||
RES_MSG = "msg0"
|
||||
CONN_SUCCESS = "sucs"
|
||||
CONN_ERROR = "fail"
|
||||
TEST_FLAG = "tst"
|
||||
CONN_TCP = "tcp"
|
||||
CONN_UDP = "udp"
|
||||
UnauthorizedBytes = `HTTP/1.1 401 Unauthorized
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
WWW-Authenticate: Basic realm="easyProxy"
|
||||
|
||||
401 Unauthorized`
|
||||
IO_EOF = "PROXYEOF"
|
||||
IO_EOF = "PROXYEOF"
|
||||
ConnectionFailBytes = `HTTP/1.1 404 Not Found
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
//copy
|
||||
func Relay(in, out net.Conn, compressType int, crypt, mux bool) {
|
||||
func Relay(in, out net.Conn, compressType int, crypt, mux bool) (n int64, err error) {
|
||||
switch compressType {
|
||||
case COMPRESS_SNAPY_ENCODE:
|
||||
copyBuffer(NewSnappyConn(in, crypt), out)
|
||||
n, err = copyBuffer(NewSnappyConn(in, crypt), out)
|
||||
out.Close()
|
||||
NewSnappyConn(in, crypt).Write([]byte(IO_EOF))
|
||||
case COMPRESS_SNAPY_DECODE:
|
||||
copyBuffer(in, NewSnappyConn(out, crypt))
|
||||
n, err = copyBuffer(in, NewSnappyConn(out, crypt))
|
||||
in.Close()
|
||||
if !mux {
|
||||
out.Close()
|
||||
}
|
||||
case COMPRESS_NONE_ENCODE:
|
||||
copyBuffer(NewCryptConn(in, crypt), out)
|
||||
n, err = copyBuffer(NewCryptConn(in, crypt), out)
|
||||
out.Close()
|
||||
NewCryptConn(in, crypt).Write([]byte(IO_EOF))
|
||||
case COMPRESS_NONE_DECODE:
|
||||
copyBuffer(in, NewCryptConn(out, crypt))
|
||||
n, err = copyBuffer(in, NewCryptConn(out, crypt))
|
||||
in.Close()
|
||||
if !mux {
|
||||
out.Close()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//判断压缩方式
|
||||
|
@ -145,7 +151,6 @@ func GetIntNoErrByStr(str string) int {
|
|||
return i
|
||||
}
|
||||
|
||||
|
||||
// io.copy的优化版,读取buffer长度原为32*1024,与snappy不同,导致读取出的内容存在差异,不利于解密
|
||||
//内存优化 用到pool,快速回收
|
||||
func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
|
@ -167,7 +172,7 @@ func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
|||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
bufPoolCopy.Put(buf)
|
||||
}
|
||||
if er != nil {
|
||||
|
@ -199,15 +204,17 @@ func Getverifyval(vkey string) string {
|
|||
}
|
||||
|
||||
//wait replay group
|
||||
func ReplayWaitGroup(conn1 net.Conn, conn2 net.Conn, compressEncode, compressDecode int, crypt, mux bool) {
|
||||
//conn1 网桥 conn2
|
||||
func ReplayWaitGroup(conn1 net.Conn, conn2 net.Conn, compressEncode, compressDecode int, crypt, mux bool) (out int64, in int64) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Relay(conn1, conn2, compressEncode, crypt, mux)
|
||||
in, _ = Relay(conn1, conn2, compressEncode, crypt, mux)
|
||||
wg.Done()
|
||||
}()
|
||||
Relay(conn2, conn1, compressDecode, crypt, mux)
|
||||
out, _ = Relay(conn2, conn1, compressDecode, crypt, mux)
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string) {
|
||||
|
@ -227,3 +234,12 @@ func ChangeHostAndHeader(r *http.Request, host string, header string, addr strin
|
|||
r.Header.Set("X-Forwarded-For", addr)
|
||||
r.Header.Set("X-Real-IP", addr)
|
||||
}
|
||||
|
||||
func ReadAllFromFile(filePth string) ([]byte, error) {
|
||||
f, err := os.Open(filePth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutil.ReadAll(f)
|
||||
}
|
||||
|
|
|
@ -27,14 +27,18 @@ func (s *BaseController) Prepare() {
|
|||
//加载模板
|
||||
func (s *BaseController) display(tpl ...string) {
|
||||
var tplname string
|
||||
if s.Data["menu"] == nil {
|
||||
s.Data["menu"] = s.actionName
|
||||
}
|
||||
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"] = utils.GetHostByName(ip[0:strings.LastIndex(ip, ":")])
|
||||
if strings.LastIndex(ip, ":") > 0 {
|
||||
s.Data["ip"] = utils.GetHostByName(ip[0:])
|
||||
}
|
||||
s.Data["p"] = server.Bridge.TunnelPort
|
||||
s.Data["proxyPort"] = beego.AppConfig.String("hostPort")
|
||||
s.Layout = "public/layout.html"
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/cnlh/easyProxy/server"
|
||||
"github.com/cnlh/easyProxy/utils"
|
||||
)
|
||||
|
||||
type ClientController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (s *ClientController) Client() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["menu"] = "client"
|
||||
s.SetInfo("客户端管理")
|
||||
s.display("client/list")
|
||||
return
|
||||
}
|
||||
start, length := s.GetAjaxParams()
|
||||
list, cnt := server.GetClientList(start, length)
|
||||
s.AjaxTable(list, cnt, cnt)
|
||||
}
|
||||
|
||||
//添加客户端
|
||||
func (s *ClientController) Add() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["menu"] = "client"
|
||||
s.SetInfo("新增")
|
||||
s.display()
|
||||
} else {
|
||||
t := &utils.Client{
|
||||
VerifyKey: utils.GetRandomString(16),
|
||||
Id: server.CsvDb.GetClientId(),
|
||||
Status: true,
|
||||
Remark: s.GetString("Remark"),
|
||||
Cnf: &utils.ServerConfig{
|
||||
U: s.GetString("u"),
|
||||
P: s.GetString("p"),
|
||||
Compress: s.GetString("compress"),
|
||||
Crypt: s.GetBoolNoErr("crypt"),
|
||||
Mux: s.GetBoolNoErr("mux"),
|
||||
},
|
||||
}
|
||||
server.CsvDb.NewClient(t)
|
||||
s.AjaxOk("添加成功")
|
||||
}
|
||||
}
|
||||
|
||||
//修改客户端
|
||||
func (s *ClientController) Edit() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["menu"] = "client"
|
||||
id := s.GetIntNoErr("id")
|
||||
if c, err := server.CsvDb.GetClient(id); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
s.Data["c"] = c
|
||||
}
|
||||
s.SetInfo("修改")
|
||||
s.display()
|
||||
} else {
|
||||
id := s.GetIntNoErr("Id")
|
||||
if c, err := server.CsvDb.GetClient(id); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
c.Remark = s.GetString("Remark")
|
||||
c.Cnf.U = s.GetString("u")
|
||||
c.Cnf.P = s.GetString("p")
|
||||
c.Cnf.Compress = s.GetString("compress")
|
||||
c.Cnf.Crypt = s.GetBoolNoErr("crypt")
|
||||
c.Cnf.Mux = s.GetBoolNoErr("mux")
|
||||
server.CsvDb.UpdateClient(c)
|
||||
}
|
||||
s.AjaxOk("修改成功")
|
||||
}
|
||||
}
|
||||
|
||||
//更改状态
|
||||
func (s *ClientController) ChangeStatus() {
|
||||
id := s.GetIntNoErr("id")
|
||||
if client, err := server.CsvDb.GetClient(id); err == nil {
|
||||
client.Status = s.GetBoolNoErr("status")
|
||||
if client.Status == false {
|
||||
server.DelClientConnect(client.Id)
|
||||
}
|
||||
s.AjaxOk("修改成功")
|
||||
}
|
||||
s.AjaxErr("修改失败")
|
||||
}
|
||||
|
||||
//删除客户端
|
||||
func (s *ClientController) Del() {
|
||||
id := s.GetIntNoErr("id")
|
||||
if err := server.CsvDb.DelClient(id); err != nil {
|
||||
s.AjaxErr("删除失败")
|
||||
}
|
||||
server.DelTunnelAndHostByClientId(id)
|
||||
server.DelClientConnect(id)
|
||||
s.AjaxOk("删除成功")
|
||||
}
|
|
@ -10,9 +10,14 @@ type IndexController struct {
|
|||
}
|
||||
|
||||
func (s *IndexController) Index() {
|
||||
s.SetInfo("使用说明")
|
||||
s.Data["data"] = server.GetDashboardData()
|
||||
s.SetInfo("dashboard")
|
||||
s.display("index/index")
|
||||
}
|
||||
func (s *IndexController) Help() {
|
||||
s.SetInfo("使用说明")
|
||||
s.display("index/help")
|
||||
}
|
||||
|
||||
func (s *IndexController) Tcp() {
|
||||
s.SetInfo("tcp隧道管理")
|
||||
|
@ -44,30 +49,44 @@ func (s *IndexController) Host() {
|
|||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) All() {
|
||||
s.Data["menu"] = "client"
|
||||
clientId := s.GetString("client_id")
|
||||
s.Data["client_id"] = clientId
|
||||
s.SetInfo("客户端" + clientId + "的所有隧道")
|
||||
s.display("index/list")
|
||||
}
|
||||
|
||||
func (s *IndexController) GetServerConfig() {
|
||||
start, length := s.GetAjaxParams()
|
||||
taskType := s.GetString("type")
|
||||
list, cnt := server.GetServerConfig(start, length, taskType)
|
||||
clientId := s.GetIntNoErr("client_id")
|
||||
list, cnt := server.GetServerConfig(start, length, taskType, clientId)
|
||||
s.AjaxTable(list, cnt, cnt)
|
||||
}
|
||||
|
||||
func (s *IndexController) Add() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["type"] = s.GetString("type")
|
||||
s.Data["client_id"] = s.GetString("client_id")
|
||||
s.SetInfo("新增")
|
||||
s.display()
|
||||
} else {
|
||||
t := &server.ServerConfig{
|
||||
TcpPort: s.GetIntNoErr("port"),
|
||||
Mode: s.GetString("type"),
|
||||
Target: s.GetString("target"),
|
||||
VerifyKey: utils.GetRandomString(16),
|
||||
U: s.GetString("u"),
|
||||
P: s.GetString("p"),
|
||||
Compress: s.GetString("compress"),
|
||||
Crypt: utils.GetBoolByStr(s.GetString("crypt")),
|
||||
Mux: utils.GetBoolByStr(s.GetString("mux")),
|
||||
IsRun: 0,
|
||||
t := &utils.ServerConfig{
|
||||
TcpPort: s.GetIntNoErr("port"),
|
||||
Mode: s.GetString("type"),
|
||||
Target: s.GetString("target"),
|
||||
U: s.GetString("u"),
|
||||
P: s.GetString("p"),
|
||||
Compress: s.GetString("compress"),
|
||||
Crypt: s.GetBoolNoErr("crypt"),
|
||||
Mux: s.GetBoolNoErr("mux"),
|
||||
IsRun: 0,
|
||||
Id: server.CsvDb.GetTaskId(),
|
||||
ClientId: s.GetIntNoErr("client_id"),
|
||||
UseClientCnf: s.GetBoolNoErr("use_client"),
|
||||
Start: 1,
|
||||
Remark: s.GetString("remark"),
|
||||
}
|
||||
server.CsvDb.NewTask(t)
|
||||
if err := server.AddTask(t); err != nil {
|
||||
|
@ -79,9 +98,9 @@ func (s *IndexController) Add() {
|
|||
}
|
||||
|
||||
func (s *IndexController) Edit() {
|
||||
id := s.GetIntNoErr("id")
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
vKey := s.GetString("vKey")
|
||||
if t, err := server.CsvDb.GetTask(vKey); err != nil {
|
||||
if t, err := server.CsvDb.GetTask(id); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
s.Data["t"] = t
|
||||
|
@ -89,44 +108,46 @@ func (s *IndexController) Edit() {
|
|||
s.SetInfo("修改")
|
||||
s.display()
|
||||
} else {
|
||||
vKey := s.GetString("vKey")
|
||||
if t, err := server.CsvDb.GetTask(vKey); err != nil {
|
||||
if t, err := server.CsvDb.GetTask(id); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
t.TcpPort = s.GetIntNoErr("port")
|
||||
t.Mode = s.GetString("type")
|
||||
t.Target = s.GetString("target")
|
||||
t.Id = id
|
||||
t.ClientId = s.GetIntNoErr("client_id")
|
||||
t.U = s.GetString("u")
|
||||
t.P = s.GetString("p")
|
||||
t.Compress = s.GetString("compress")
|
||||
t.Crypt = utils.GetBoolByStr(s.GetString("crypt"))
|
||||
t.Mux = utils.GetBoolByStr(s.GetString("mux"))
|
||||
t.Crypt = s.GetBoolNoErr("crypt")
|
||||
t.Mux = s.GetBoolNoErr("mux")
|
||||
t.UseClientCnf = s.GetBoolNoErr("use_client")
|
||||
t.Remark = s.GetString("remark")
|
||||
server.CsvDb.UpdateTask(t)
|
||||
server.StopServer(t.VerifyKey)
|
||||
server.StartTask(t.VerifyKey)
|
||||
}
|
||||
s.AjaxOk("修改成功")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IndexController) Stop() {
|
||||
vKey := s.GetString("vKey")
|
||||
if err := server.StopServer(vKey); err != nil {
|
||||
id := s.GetIntNoErr("id")
|
||||
if err := server.StopServer(id); err != nil {
|
||||
s.AjaxErr("停止失败")
|
||||
}
|
||||
s.AjaxOk("停止成功")
|
||||
}
|
||||
|
||||
func (s *IndexController) Del() {
|
||||
vKey := s.GetString("vKey")
|
||||
if err := server.DelTask(vKey); err != nil {
|
||||
id := s.GetIntNoErr("id")
|
||||
if err := server.DelTask(id); err != nil {
|
||||
s.AjaxErr("删除失败")
|
||||
}
|
||||
s.AjaxOk("删除成功")
|
||||
}
|
||||
|
||||
func (s *IndexController) Start() {
|
||||
vKey := s.GetString("vKey")
|
||||
if err := server.StartTask(vKey); err != nil {
|
||||
id := s.GetIntNoErr("id")
|
||||
if err := server.StartTask(id); err != nil {
|
||||
s.AjaxErr("开启失败")
|
||||
}
|
||||
s.AjaxOk("开启成功")
|
||||
|
@ -134,13 +155,14 @@ func (s *IndexController) Start() {
|
|||
|
||||
func (s *IndexController) HostList() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["vkey"] = s.GetString("vkey")
|
||||
s.Data["client_id"] = s.GetString("client_id")
|
||||
s.Data["menu"] = "host"
|
||||
s.SetInfo("域名列表")
|
||||
s.display("index/hlist")
|
||||
} else {
|
||||
start, length := s.GetAjaxParams()
|
||||
vkey := s.GetString("vkey")
|
||||
list, cnt := server.CsvDb.GetHostList(start, length, vkey)
|
||||
clientId := s.GetIntNoErr("client_id")
|
||||
list, cnt := server.CsvDb.GetHostList(start, length, clientId)
|
||||
s.AjaxTable(list, cnt, cnt)
|
||||
}
|
||||
}
|
||||
|
@ -155,16 +177,18 @@ func (s *IndexController) DelHost() {
|
|||
|
||||
func (s *IndexController) AddHost() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
s.Data["vkey"] = s.GetString("vkey")
|
||||
s.Data["client_id"] = s.GetString("client_id")
|
||||
s.Data["menu"] = "host"
|
||||
s.SetInfo("新增")
|
||||
s.display("index/hadd")
|
||||
} else {
|
||||
h := &server.HostList{
|
||||
Vkey: s.GetString("vkey"),
|
||||
h := &utils.HostList{
|
||||
ClientId: s.GetIntNoErr("client_id"),
|
||||
Host: s.GetString("host"),
|
||||
Target: s.GetString("target"),
|
||||
HeaderChange: s.GetString("header"),
|
||||
HostChange: s.GetString("hostchange"),
|
||||
Remark: s.GetString("remark"),
|
||||
}
|
||||
server.CsvDb.NewHost(h)
|
||||
s.AjaxOk("添加成功")
|
||||
|
@ -172,26 +196,26 @@ func (s *IndexController) AddHost() {
|
|||
}
|
||||
|
||||
func (s *IndexController) EditHost() {
|
||||
host := s.GetString("host")
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
host := s.GetString("host")
|
||||
if h, t, err := server.GetKeyByHost(host); err != nil {
|
||||
s.Data["menu"] = "host"
|
||||
if h, _, err := server.GetKeyByHost(host); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
s.Data["t"] = t
|
||||
s.Data["h"] = h
|
||||
}
|
||||
s.SetInfo("修改")
|
||||
s.display("index/hedit")
|
||||
} else {
|
||||
host := s.GetString("host")
|
||||
if h, _, err := server.GetKeyByHost(host); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
h.Vkey = s.GetString("vkey")
|
||||
h.ClientId = s.GetIntNoErr("client_id")
|
||||
h.Host = s.GetString("nhost")
|
||||
h.Target = s.GetString("target")
|
||||
h.HeaderChange = s.GetString("header")
|
||||
h.HostChange = s.GetString("hostchange")
|
||||
h.Remark = s.GetString("remark")
|
||||
server.CsvDb.UpdateHost(h)
|
||||
}
|
||||
s.AjaxOk("修改成功")
|
||||
|
|
|
@ -9,4 +9,5 @@ func init() {
|
|||
beego.Router("/", &controllers.IndexController{}, "*:Index")
|
||||
beego.AutoRouter(&controllers.IndexController{})
|
||||
beego.AutoRouter(&controllers.LoginController{})
|
||||
beego.AutoRouter(&controllers.ClientController{})
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,10 +4,10 @@
|
|||
*
|
||||
* To rebuild or modify this file with the latest versions of the included
|
||||
* software please visit:
|
||||
* https://datatables.net/download/#bs4/jq-3.3.1/dt-1.10.18
|
||||
* https://datatables.net/download/#bs4/jq-3.3.1/dt-1.10.18/r-2.2.2
|
||||
*
|
||||
* Included libraries:
|
||||
* jQuery 3 3.3.1, DataTables 1.10.18
|
||||
* jQuery 3 3.3.1, DataTables 1.10.18, Responsive 2.2.2
|
||||
*/
|
||||
|
||||
/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
|
||||
|
@ -164,7 +164,7 @@ Z(n.defaults.column);n.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender
|
|||
aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",
|
||||
iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:
|
||||
this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};n.ext=x={buttons:{},
|
||||
classes:{},build:"bs4/jq-3.3.1/dt-1.10.18",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:n.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:n.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});
|
||||
classes:{},build:"bs4/jq-3.3.1/dt-1.10.18/r-2.2.2",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:n.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:n.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});
|
||||
h.extend(n.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",
|
||||
sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",
|
||||
sJUIHeader:"",sJUIFooter:""});var Kb=n.ext.pager;h.extend(Kb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ia(a,b)]},simple_numbers:function(a,b){return["previous",ia(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ia(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ia(a,b),"last"]},_numbers:ia,numbers_length:7});h.extend(!0,n.ext.renderer,{pageButton:{_:function(a,b,c,d,e,
|
||||
|
@ -192,3 +192,43 @@ renderer:"bootstrap"});b.extend(f.ext.classes,{sWrapper:"dataTables_wrapper dt-b
|
|||
{"class":t.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("<a>",{href:"#","aria-controls":a.sTableId,"aria-label":u[c],"data-dt-idx":p,tabindex:a.iTabIndex,"class":"page-link"}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(v){}q(b(h).empty().html('<ul class="pagination"/>').children("ul"),s);i!==m&&b(h).find("[data-dt-idx="+i+"]").focus()};return f});
|
||||
|
||||
|
||||
/*!
|
||||
Responsive 2.2.2
|
||||
2014-2018 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
(function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(l){return d(l,window,document)}):"object"===typeof exports?module.exports=function(l,j){l||(l=window);if(!j||!j.fn.dataTable)j=require("datatables.net")(l,j).$;return d(j,l,l.document)}:d(jQuery,window,document)})(function(d,l,j,q){function t(a,b,c){var e=b+"-"+c;if(n[e])return n[e];for(var d=[],a=a.cell(b,c).node().childNodes,b=0,c=a.length;b<c;b++)d.push(a[b]);return n[e]=d}function r(a,b,d){var e=b+
|
||||
"-"+d;if(n[e]){for(var a=a.cell(b,d).node(),d=n[e][0].parentNode.childNodes,b=[],f=0,g=d.length;f<g;f++)b.push(d[f]);d=0;for(f=b.length;d<f;d++)a.appendChild(b[d]);n[e]=q}}var o=d.fn.dataTable,i=function(a,b){if(!o.versionCheck||!o.versionCheck("1.10.10"))throw"DataTables Responsive requires DataTables 1.10.10 or newer";this.s={dt:new o.Api(a),columns:[],current:[]};this.s.dt.settings()[0].responsive||(b&&"string"===typeof b.details?b.details={type:b.details}:b&&!1===b.details?b.details={type:!1}:
|
||||
b&&!0===b.details&&(b.details={type:"inline"}),this.c=d.extend(!0,{},i.defaults,o.defaults.responsive,b),a.responsive=this,this._constructor())};d.extend(i.prototype,{_constructor:function(){var a=this,b=this.s.dt,c=b.settings()[0],e=d(l).width();b.settings()[0]._responsive=this;d(l).on("resize.dtr orientationchange.dtr",o.util.throttle(function(){var b=d(l).width();b!==e&&(a._resize(),e=b)}));c.oApi._fnCallbackReg(c,"aoRowCreatedCallback",function(e){-1!==d.inArray(!1,a.s.current)&&d(">td, >th",
|
||||
e).each(function(e){e=b.column.index("toData",e);!1===a.s.current[e]&&d(this).css("display","none")})});b.on("destroy.dtr",function(){b.off(".dtr");d(b.table().body()).off(".dtr");d(l).off("resize.dtr orientationchange.dtr");d.each(a.s.current,function(b,e){!1===e&&a._setColumnVis(b,!0)})});this.c.breakpoints.sort(function(a,b){return a.width<b.width?1:a.width>b.width?-1:0});this._classLogic();this._resizeAuto();c=this.c.details;!1!==c.type&&(a._detailsInit(),b.on("column-visibility.dtr",function(){a._timer&&
|
||||
clearTimeout(a._timer);a._timer=setTimeout(function(){a._timer=null;a._classLogic();a._resizeAuto();a._resize();a._redrawChildren()},100)}),b.on("draw.dtr",function(){a._redrawChildren()}),d(b.table().node()).addClass("dtr-"+c.type));b.on("column-reorder.dtr",function(){a._classLogic();a._resizeAuto();a._resize()});b.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});b.on("preXhr.dtr",function(){var e=[];b.rows().every(function(){this.child.isShown()&&e.push(this.id(true))});b.one("draw.dtr",
|
||||
function(){a._resizeAuto();a._resize();b.rows(e).every(function(){a._detailsDisplay(this,false)})})});b.on("init.dtr",function(){a._resizeAuto();a._resize();d.inArray(false,a.s.current)&&b.columns.adjust()});this._resize()},_columnsVisiblity:function(a){var b=this.s.dt,c=this.s.columns,e,f,g=c.map(function(a,b){return{columnIdx:b,priority:a.priority}}).sort(function(a,b){return a.priority!==b.priority?a.priority-b.priority:a.columnIdx-b.columnIdx}),h=d.map(c,function(e,c){return!1===b.column(c).visible()?
|
||||
"not-visible":e.auto&&null===e.minWidth?!1:!0===e.auto?"-":-1!==d.inArray(a,e.includeIn)}),m=0;e=0;for(f=h.length;e<f;e++)!0===h[e]&&(m+=c[e].minWidth);e=b.settings()[0].oScroll;e=e.sY||e.sX?e.iBarWidth:0;m=b.table().container().offsetWidth-e-m;e=0;for(f=h.length;e<f;e++)c[e].control&&(m-=c[e].minWidth);var s=!1;e=0;for(f=g.length;e<f;e++){var k=g[e].columnIdx;"-"===h[k]&&(!c[k].control&&c[k].minWidth)&&(s||0>m-c[k].minWidth?(s=!0,h[k]=!1):h[k]=!0,m-=c[k].minWidth)}g=!1;e=0;for(f=c.length;e<f;e++)if(!c[e].control&&
|
||||
!c[e].never&&!1===h[e]){g=!0;break}e=0;for(f=c.length;e<f;e++)c[e].control&&(h[e]=g),"not-visible"===h[e]&&(h[e]=!1);-1===d.inArray(!0,h)&&(h[0]=!0);return h},_classLogic:function(){var a=this,b=this.c.breakpoints,c=this.s.dt,e=c.columns().eq(0).map(function(a){var b=this.column(a),e=b.header().className,a=c.settings()[0].aoColumns[a].responsivePriority;a===q&&(b=d(b.header()).data("priority"),a=b!==q?1*b:1E4);return{className:e,includeIn:[],auto:!1,control:!1,never:e.match(/\bnever\b/)?!0:!1,priority:a}}),
|
||||
f=function(a,b){var c=e[a].includeIn;-1===d.inArray(b,c)&&c.push(b)},g=function(d,c,g,k){if(g)if("max-"===g){k=a._find(c).width;c=0;for(g=b.length;c<g;c++)b[c].width<=k&&f(d,b[c].name)}else if("min-"===g){k=a._find(c).width;c=0;for(g=b.length;c<g;c++)b[c].width>=k&&f(d,b[c].name)}else{if("not-"===g){c=0;for(g=b.length;c<g;c++)-1===b[c].name.indexOf(k)&&f(d,b[c].name)}}else e[d].includeIn.push(c)};e.each(function(a,e){for(var c=a.className.split(" "),f=!1,i=0,l=c.length;i<l;i++){var j=d.trim(c[i]);
|
||||
if("all"===j){f=!0;a.includeIn=d.map(b,function(a){return a.name});return}if("none"===j||a.never){f=!0;return}if("control"===j){f=!0;a.control=!0;return}d.each(b,function(a,b){var d=b.name.split("-"),c=j.match(RegExp("(min\\-|max\\-|not\\-)?("+d[0]+")(\\-[_a-zA-Z0-9])?"));c&&(f=!0,c[2]===d[0]&&c[3]==="-"+d[1]?g(e,b.name,c[1],c[2]+c[3]):c[2]===d[0]&&!c[3]&&g(e,b.name,c[1],c[2]))})}f||(a.auto=!0)});this.s.columns=e},_detailsDisplay:function(a,b){var c=this,e=this.s.dt,f=this.c.details;if(f&&!1!==f.type){var g=
|
||||
f.display(a,b,function(){return f.renderer(e,a[0],c._detailsObj(a[0]))});(!0===g||!1===g)&&d(e.table().node()).triggerHandler("responsive-display.dt",[e,a,g,b])}},_detailsInit:function(){var a=this,b=this.s.dt,c=this.c.details;"inline"===c.type&&(c.target="td:first-child, th:first-child");b.on("draw.dtr",function(){a._tabIndexes()});a._tabIndexes();d(b.table().body()).on("keyup.dtr","td, th",function(a){a.keyCode===13&&d(this).data("dtr-keyboard")&&d(this).click()});var e=c.target;d(b.table().body()).on("click.dtr mousedown.dtr mouseup.dtr",
|
||||
"string"===typeof e?e:"td, th",function(c){if(d(b.table().node()).hasClass("collapsed")&&d.inArray(d(this).closest("tr").get(0),b.rows().nodes().toArray())!==-1){if(typeof e==="number"){var g=e<0?b.columns().eq(0).length+e:e;if(b.cell(this).index().column!==g)return}g=b.row(d(this).closest("tr"));c.type==="click"?a._detailsDisplay(g,false):c.type==="mousedown"?d(this).css("outline","none"):c.type==="mouseup"&&d(this).blur().css("outline","")}})},_detailsObj:function(a){var b=this,c=this.s.dt;return d.map(this.s.columns,
|
||||
function(e,d){if(!e.never&&!e.control)return{title:c.settings()[0].aoColumns[d].sTitle,data:c.cell(a,d).render(b.c.orthogonal),hidden:c.column(d).visible()&&!b.s.current[d],columnIndex:d,rowIndex:a}})},_find:function(a){for(var b=this.c.breakpoints,c=0,e=b.length;c<e;c++)if(b[c].name===a)return b[c]},_redrawChildren:function(){var a=this,b=this.s.dt;b.rows({page:"current"}).iterator("row",function(c,e){b.row(e);a._detailsDisplay(b.row(e),!0)})},_resize:function(){var a=this,b=this.s.dt,c=d(l).width(),
|
||||
e=this.c.breakpoints,f=e[0].name,g=this.s.columns,h,m=this.s.current.slice();for(h=e.length-1;0<=h;h--)if(c<=e[h].width){f=e[h].name;break}var i=this._columnsVisiblity(f);this.s.current=i;e=!1;h=0;for(c=g.length;h<c;h++)if(!1===i[h]&&!g[h].never&&!g[h].control&&!1===!b.column(h).visible()){e=!0;break}d(b.table().node()).toggleClass("collapsed",e);var k=!1,j=0;b.columns().eq(0).each(function(b,c){!0===i[c]&&j++;i[c]!==m[c]&&(k=!0,a._setColumnVis(b,i[c]))});k&&(this._redrawChildren(),d(b.table().node()).trigger("responsive-resize.dt",
|
||||
[b,this.s.current]),0===b.page.info().recordsDisplay&&d("td",b.table().body()).eq(0).attr("colspan",j))},_resizeAuto:function(){var a=this.s.dt,b=this.s.columns;if(this.c.auto&&-1!==d.inArray(!0,d.map(b,function(a){return a.auto}))){d.isEmptyObject(n)||d.each(n,function(b){b=b.split("-");r(a,1*b[0],1*b[1])});a.table().node();var c=a.table().node().cloneNode(!1),e=d(a.table().header().cloneNode(!1)).appendTo(c),f=d(a.table().body()).clone(!1,!1).empty().appendTo(c),g=a.columns().header().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display",
|
||||
"table-cell").css("min-width",0);d(f).append(d(a.rows({page:"current"}).nodes()).clone(!1)).find("th, td").css("display","");if(f=a.table().footer()){var f=d(f.cloneNode(!1)).appendTo(c),h=a.columns().footer().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display","table-cell");d("<tr/>").append(h).appendTo(f)}d("<tr/>").append(g).appendTo(e);"inline"===this.c.details.type&&d(c).addClass("dtr-inline collapsed");d(c).find("[name]").removeAttr("name");d(c).css("position","relative");
|
||||
c=d("<div/>").css({width:1,height:1,overflow:"hidden",clear:"both"}).append(c);c.insertBefore(a.table().node());g.each(function(c){c=a.column.index("fromVisible",c);b[c].minWidth=this.offsetWidth||0});c.remove()}},_setColumnVis:function(a,b){var c=this.s.dt,e=b?"":"none";d(c.column(a).header()).css("display",e);d(c.column(a).footer()).css("display",e);c.column(a).nodes().to$().css("display",e);d.isEmptyObject(n)||c.cells(null,a).indexes().each(function(a){r(c,a.row,a.column)})},_tabIndexes:function(){var a=
|
||||
this.s.dt,b=a.cells({page:"current"}).nodes().to$(),c=a.settings()[0],e=this.c.details.target;b.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");"number"===typeof e?a.cells(null,e,{page:"current"}).nodes().to$().attr("tabIndex",c.iTabIndex).data("dtr-keyboard",1):("td:first-child, th:first-child"===e&&(e=">td:first-child, >th:first-child"),d(e,a.rows({page:"current"}).nodes()).attr("tabIndex",c.iTabIndex).data("dtr-keyboard",1))}});i.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",
|
||||
width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];i.display={childRow:function(a,b,c){if(b){if(d(a.node()).hasClass("parent"))return a.child(c(),"child").show(),!0}else{if(a.child.isShown())return a.child(!1),d(a.node()).removeClass("parent"),!1;a.child(c(),"child").show();d(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,b,c){if(!b&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),d(a.node()).removeClass("parent"),
|
||||
!1;a.child(c(),"child").show();d(a.node()).addClass("parent");return!0},modal:function(a){return function(b,c,e){if(c)d("div.dtr-modal-content").empty().append(e());else{var f=function(){g.remove();d(j).off("keypress.dtr")},g=d('<div class="dtr-modal"/>').append(d('<div class="dtr-modal-display"/>').append(d('<div class="dtr-modal-content"/>').append(e())).append(d('<div class="dtr-modal-close">×</div>').click(function(){f()}))).append(d('<div class="dtr-modal-background"/>').click(function(){f()})).appendTo("body");
|
||||
d(j).on("keyup.dtr",function(a){27===a.keyCode&&(a.stopPropagation(),f())})}a&&a.header&&d("div.dtr-modal-content").prepend("<h2>"+a.header(b)+"</h2>")}}};var n={};i.renderer={listHiddenNodes:function(){return function(a,b,c){var e=d('<ul data-dtr-index="'+b+'" class="dtr-details"/>'),f=!1;d.each(c,function(b,c){c.hidden&&(d('<li data-dtr-index="'+c.columnIndex+'" data-dt-row="'+c.rowIndex+'" data-dt-column="'+c.columnIndex+'"><span class="dtr-title">'+c.title+"</span> </li>").append(d('<span class="dtr-data"/>').append(t(a,
|
||||
c.rowIndex,c.columnIndex))).appendTo(e),f=!0)});return f?e:!1}},listHidden:function(){return function(a,b,c){return(a=d.map(c,function(a){return a.hidden?'<li data-dtr-index="'+a.columnIndex+'" data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><span class="dtr-title">'+a.title+'</span> <span class="dtr-data">'+a.data+"</span></li>":""}).join(""))?d('<ul data-dtr-index="'+b+'" class="dtr-details"/>').append(a):!1}},tableAll:function(a){a=d.extend({tableClass:""},a);return function(b,
|
||||
c,e){b=d.map(e,function(a){return'<tr data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><td>'+a.title+":</td> <td>"+a.data+"</td></tr>"}).join("");return d('<table class="'+a.tableClass+' dtr-details" width="100%"/>').append(b)}}};i.defaults={breakpoints:i.breakpoints,auto:!0,details:{display:i.display.childRow,renderer:i.renderer.listHidden(),target:0,type:"inline"},orthogonal:"display"};var p=d.fn.dataTable.Api;p.register("responsive()",function(){return this});p.register("responsive.index()",
|
||||
function(a){a=d(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});p.register("responsive.rebuild()",function(){return this.iterator("table",function(a){a._responsive&&a._responsive._classLogic()})});p.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});p.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==d.inArray(!1,a._responsive.s.current):
|
||||
!1});p.registerPlural("columns().responsiveHidden()","column().responsiveHidden()",function(){return this.iterator("column",function(a,b){return a._responsive?a._responsive.s.current[b]:!1},1)});i.version="2.2.2";d.fn.dataTable.Responsive=i;d.fn.DataTable.Responsive=i;d(j).on("preInit.dt.dtr",function(a,b){if("dt"===a.namespace&&(d(b.nTable).hasClass("responsive")||d(b.nTable).hasClass("dt-responsive")||b.oInit.responsive||o.defaults.responsive)){var c=b.oInit.responsive;!1!==c&&new i(b,d.isPlainObject(c)?
|
||||
c:{})}});return i});
|
||||
|
||||
|
||||
/*!
|
||||
Bootstrap 4 integration for DataTables' Responsive
|
||||
©2016 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-responsive"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);if(!b||!b.fn.dataTable)b=require("datatables.net-bs4")(a,b).$;b.fn.dataTable.Responsive||require("datatables.net-responsive")(a,b);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c){var a=c.fn.dataTable,b=a.Responsive.display,g=b.modal,e=c('<div class="modal fade dtr-bs-modal" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="modal-body"/></div></div></div>');
|
||||
b.modal=function(a){return function(b,d,f){if(c.fn.modal){if(!d){if(a&&a.header){var d=e.find("div.modal-header"),h=d.find("button").detach();d.empty().append('<h4 class="modal-title">'+a.header(b)+"</h4>").append(h)}e.find("div.modal-body").empty().append(f());e.appendTo("body").modal()}}else g(b,d,f)}};return a.Responsive});
|
||||
|
||||
|
||||
|
|
|
@ -1,27 +1,48 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
var treeviewMenu = $('.app-menu');
|
||||
var treeviewMenu = $('.app-menu');
|
||||
|
||||
// Toggle Sidebar
|
||||
$('[data-toggle="sidebar"]').click(function(event) {
|
||||
event.preventDefault();
|
||||
$('.app').toggleClass('sidenav-toggled');
|
||||
});
|
||||
// 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');
|
||||
});
|
||||
// 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');
|
||||
// Set initial active toggle
|
||||
$("[data-toggle='treeview.'].is-expanded").parent().toggleClass('is-expanded');
|
||||
|
||||
//Activate bootstrip tooltips
|
||||
// $("[data-toggle='tooltip']").tooltip();
|
||||
//Activate bootstrip tooltips
|
||||
// $("[data-toggle='tooltip']").tooltip();
|
||||
|
||||
})();
|
||||
|
||||
function change(limit) {
|
||||
var size = "";
|
||||
if (limit < 0.1 * 1024) {
|
||||
size = limit.toFixed(2) + "B"
|
||||
} else if (limit < 0.1 * 1024 * 1024) {
|
||||
size = (limit / 1024).toFixed(2) + "KB"
|
||||
} else if (limit < 0.1 * 1024 * 1024 * 1024) {
|
||||
size = (limit / (1024 * 1024)).toFixed(2) + "MB"
|
||||
} else {
|
||||
size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB"
|
||||
}
|
||||
|
||||
var sizeStr = size + "";
|
||||
var index = sizeStr.indexOf(".");
|
||||
var dou = sizeStr.substr(index + 1, 2)
|
||||
if (dou == "00") {
|
||||
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>easyProxy error</title>
|
||||
</head>
|
||||
<body>
|
||||
404 not found,power by easyProxy
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,67 @@
|
|||
<div class="row tile">
|
||||
<div class="col-md-6 col-md-auto">
|
||||
<div>
|
||||
<h3 class="tile-title">添加</h3>
|
||||
<div class="tile-body">
|
||||
<form>
|
||||
<div class="form-group" id="target">
|
||||
<label class="control-label">备注</label>
|
||||
<input class="form-control" type="text" name="Remark" placeholder="客户端备注">
|
||||
</div>
|
||||
<div class="form-group" id="u">
|
||||
<label class="control-label">验证用户名(仅socks5,web穿透支持)</label>
|
||||
<input class="form-control" type="text" name="u" placeholder="不填则无需验证">
|
||||
</div>
|
||||
<div class="form-group" id="p">
|
||||
<label class="control-label">验证密码(仅socks5,web穿透支持)</label>
|
||||
<input class="form-control" type="text" name="p" placeholder="不填则无需验证">
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">数据压缩方式</label>
|
||||
<select class="form-control" name="compress">
|
||||
<option value="">不压缩</option>
|
||||
<option value="snappy">snappy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">是否加密传输</label>
|
||||
<select class="form-control" name="crypt">
|
||||
<option value="0">不加密</option>
|
||||
<option value="1">加密</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">是否TCP复用</label>
|
||||
<select class="form-control" name="crypt">
|
||||
<option value="0">不启用</option>
|
||||
<option value="1">启用</option>
|
||||
</select>
|
||||
</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: "/client/add",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
history.back(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,71 @@
|
|||
<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="Id" value="{{.c.Id}}">
|
||||
<div class="form-group" id="target">
|
||||
<label class="control-label">备注</label>
|
||||
<input class="form-control" value="{{.c.Remark}}" type="text" name="Remark" placeholder="客户端备注">
|
||||
</div>
|
||||
<div class="form-group" id="u">
|
||||
<label class="control-label">验证用户名(仅socks5,web穿透支持)</label>
|
||||
<input class="form-control" value="{{.c.Cnf.U}}" type="text" name="u"
|
||||
placeholder="不填则无需验证">
|
||||
</div>
|
||||
<div class="form-group" id="p">
|
||||
<label class="control-label">验证密码(仅socks5,web穿透支持)</label>
|
||||
<input class="form-control" value="{{.c.Cnf.P}}" type="text" name="p"
|
||||
placeholder="不填则无需验证">
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">数据压缩方式(所有模式均支持)</label>
|
||||
<select class="form-control" name="compress">
|
||||
<option {{if eq "" .c.Cnf.Compress}}selected{{end}} value="">不压缩</option>
|
||||
<option {{if eq "snappy" .c.Cnf.Compress}}selected{{end}} value="snappy">snappy压缩</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">是否加密传输(所有模式均支持)</label>
|
||||
<select class="form-control" name="crypt">
|
||||
<option {{if eq false .c.Cnf.Crypt}}selected{{end}} value="0">不加密</option>
|
||||
<option {{if eq true .c.Cnf.Crypt}}selected{{end}} value="1">加密</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">是否启用TCP复用(所有模式均支持)</label>
|
||||
<select class="form-control" name="mux">
|
||||
<option {{if eq false .c.Cnf.Mux}}selected{{end}} value="0">不启用</option>
|
||||
<option {{if eq true .c.Cnf.Mux}}selected{{end}} value="1">启用</option>
|
||||
</select>
|
||||
</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: "/client/edit",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
history.back(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,234 @@
|
|||
<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>Id</th>
|
||||
<th>验证密钥</th>
|
||||
<th>客户端地址</th>
|
||||
<th>备注</th>
|
||||
<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(id) {
|
||||
if (confirm("确定要删除数据吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/client/del",
|
||||
data: {"id": id},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function start(id) {
|
||||
if (confirm("确定要开始任务吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/client/changestatus",
|
||||
data: {"id": id, "status": 1},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function stop(id) {
|
||||
if (confirm("确定要暂停吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/client/changestatus",
|
||||
data: {
|
||||
"id": id, "status": 0
|
||||
},
|
||||
success:
|
||||
|
||||
function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function edit(id) {
|
||||
window.location.href = "/client/edit?id=" + id
|
||||
}
|
||||
|
||||
function add() {
|
||||
window.location.href = "/client/add"
|
||||
}
|
||||
|
||||
function show_tunnel_list(id) {
|
||||
window.location.href = "/index/all?client_id=" + id
|
||||
}
|
||||
|
||||
function show_host_list(id) {
|
||||
window.location.href = "/index/hostlist?client_id=" + id
|
||||
}
|
||||
|
||||
$(document)
|
||||
|
||||
.ready(function () {
|
||||
var table = $('#sampleTable').DataTable({
|
||||
responsive: {
|
||||
details: {
|
||||
display: $.fn.dataTable.Responsive.display.childRowImmediate
|
||||
}
|
||||
},
|
||||
dom: 'Bfrtip',
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
autoWidth: false,
|
||||
ordering: false,
|
||||
ajax: {
|
||||
url: '/client/client',
|
||||
type: 'POST'
|
||||
},
|
||||
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
|
||||
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
|
||||
{data: 'Id'},
|
||||
{data: 'VerifyKey'},
|
||||
{data: "Addr"},
|
||||
{data: "Remark"},
|
||||
{data: "Addr"},
|
||||
{data: "Addr"},
|
||||
{data: "Addr"},
|
||||
{data: "Addr"},
|
||||
{data: "Remark"},
|
||||
{data: "Status"},
|
||||
{data: "IsConnect"},
|
||||
{data: "Addr"},
|
||||
{data: "Addr"},
|
||||
{data: ""},
|
||||
],
|
||||
bFilter: false,
|
||||
columnDefs: [{
|
||||
targets: -1,
|
||||
render: function (data, type, row, meta) {
|
||||
if (row.Status == true) {
|
||||
btn = "<button onclick=\"stop('" + row.Id + "')\" class=\"btn btn-secondary btn-sm\" type=\"button\">关闭</button>"
|
||||
} else {
|
||||
btn = "<button onclick=\"start('" + row.Id + "')\" class=\"btn btn-success btn-sm\" type=\"button\">打开</button>"
|
||||
}
|
||||
btn_del = '<button onclick="del(\'' + row.Id + '\')" class="btn btn-danger btn-sm">删除</button> '
|
||||
btn_edit = '<button onclick="edit(\'' + row.Id + '\')" class="btn btn-primary btn-sm">查看编辑</button> '
|
||||
btn_list = '<button onclick="show_tunnel_list(\'' + row.Id + '\')" class="btn btn-info btn-sm">隧道</button> '
|
||||
btn_host = '<button onclick="show_host_list(\'' + row.Id + '\')" class="btn btn-info btn-sm">域名</button> '
|
||||
return '<div class="btn-group" role="group" aria-label="..."> ' + btn + btn_del + btn_edit + btn_host + btn_list + '</div>'
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -5,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == false) {
|
||||
return "<span class=\"badge badge-pill badge-secondary\">暂停</span>"
|
||||
} else {
|
||||
return "<span class=\"badge badge-pill badge-success\">正常</span>"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -4,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == false) {
|
||||
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 change(row.Flow.InletFlow)
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -3,
|
||||
render: function (data, type, row, meta) {
|
||||
return change(row.Flow.ExportFlow)
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -6,
|
||||
render: function (data, type, row, meta) {
|
||||
return row.Cnf.P
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -7,
|
||||
render: function (data, type, row, meta) {
|
||||
return row.Cnf.U
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -8,
|
||||
render: function (data, type, row, meta) {
|
||||
if (row.Cnf.Mux == "0") {
|
||||
return "不启用"
|
||||
} else {
|
||||
return "启用"
|
||||
}
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
targets: -9,
|
||||
render: function (data, type, row, meta) {
|
||||
if (row.Cnf.Crypt == "0") {
|
||||
return "不加密"
|
||||
} else {
|
||||
return "加密"
|
||||
}
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
targets: -10,
|
||||
render: function (data, type, row, meta) {
|
||||
return row.Cnf.Compress
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
buttons: []
|
||||
});
|
||||
$("#sampleTable_length").html('<button class="btn btn-primary" onclick="add()" type="button">新增</button>')
|
||||
})
|
||||
;
|
||||
|
||||
|
||||
</script>
|
|
@ -19,9 +19,12 @@
|
|||
<option {{if eq "udpServer" .type}}selected{{end}} value="udpServer">udp隧道</option>
|
||||
<option {{if eq "socks5Server" .type}}selected{{end}} value="socks5Server">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">
|
||||
<label class="control-label">备注</label>
|
||||
<input class="form-control" type="text" name="remark" placeholder="备注">
|
||||
</div>
|
||||
<div class="form-group" id="port">
|
||||
<label class="control-label">监听的端口</label>
|
||||
<input class="form-control" type="text" name="port" placeholder="公网服务器对外访问端口,例如8024">
|
||||
|
@ -30,6 +33,18 @@
|
|||
<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="client_id">
|
||||
<label class="control-label">客户端ID</label>
|
||||
<input value="{{.client_id}}" class="form-control" type="text" name="client_id"
|
||||
placeholder="客户端id">
|
||||
</div>
|
||||
<div class="form-group" id="use_client">
|
||||
<label class="control-label">是否使用客户端配置</label>
|
||||
<select class="form-control" name="use_client">
|
||||
<option value="1">是</option>
|
||||
<option value="0">否</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">数据压缩方式</label>
|
||||
<select class="form-control" name="compress">
|
||||
|
@ -37,16 +52,16 @@
|
|||
<option value="snappy">snappy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<div class="form-group" id="crypt">
|
||||
<label class="control-label">是否加密传输</label>
|
||||
<select class="form-control" name="crypt">
|
||||
<option value="0">不加密</option>
|
||||
<option value="1">加密</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<div class="form-group" id="mux">
|
||||
<label class="control-label">是否TCP复用</label>
|
||||
<select class="form-control" name="crypt">
|
||||
<select class="form-control" name="mux">
|
||||
<option value="0">不启用</option>
|
||||
<option value="1">启用</option>
|
||||
</select>
|
||||
|
@ -77,7 +92,7 @@
|
|||
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["socks5Server"] = ["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", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
|
||||
arr["hostServer"] = ["type", "compress", "u", "p", "域名分发模式,使用域名代理内网服务,适用于小程序开发、公众号开发、站点演示等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,使用nginx将请求反向代理到本程序,再进行域名配置,即可解析"]
|
||||
arrClientHide = ["compress", "u", "p", "crypt", "mux"]
|
||||
|
||||
function resetForm() {
|
||||
for (var i = 0; i < arr["all"].length; i++) {
|
||||
|
@ -90,10 +105,28 @@
|
|||
$("#info").html(arr[o][arr[o].length - 1])
|
||||
}
|
||||
|
||||
function resetClientCnf() {
|
||||
for (var i = 0; i < arrClientHide.length; i++) {
|
||||
$("#" + arrClientHide[i]).css("display", "block")
|
||||
}
|
||||
op = $("#use_client option:selected").val()
|
||||
if (op == 1) {
|
||||
for (var i = 0; i < arrClientHide.length; i++) {
|
||||
$("#" + arrClientHide[i]).css("display", "none")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
resetForm()
|
||||
resetClientCnf()
|
||||
$("#type").on("change", function () {
|
||||
resetForm()
|
||||
resetClientCnf()
|
||||
})
|
||||
$("#use_client").on("change", function () {
|
||||
resetForm()
|
||||
resetClientCnf()
|
||||
})
|
||||
$("#add").on("click", function () {
|
||||
$.ajax({
|
||||
|
|
|
@ -4,17 +4,21 @@
|
|||
<h3 class="tile-title">添加</h3>
|
||||
<div class="tile-body">
|
||||
<form>
|
||||
<input type="hidden" name="vKey" value="{{.t.VerifyKey}}">
|
||||
<input type="hidden" name="id" value="{{.t.Id}}">
|
||||
<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 "socks5Server" .t.Mode}}selected{{end}} value="socks5Server">socks5代理</option>
|
||||
<option {{if eq "socks5Server" .t.Mode}}selected{{end}} value="socks5Server">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">
|
||||
<label class="control-label">备注</label>
|
||||
<input class="form-control" value="{{.t.Remark}}" type="text" name="remark" placeholder="备注">
|
||||
</div>
|
||||
<div class="form-group" id="port">
|
||||
<label class="control-label">监听的端口</label>
|
||||
<input class="form-control" value="{{.t.TcpPort}}" type="text" name="port"
|
||||
|
@ -25,6 +29,18 @@
|
|||
<input class="form-control" value="{{.t.Target}}" type="text" name="target"
|
||||
placeholder="内网隧道目标,例如10.1.50.203:22">
|
||||
</div>
|
||||
<div class="form-group" id="client_id">
|
||||
<label class="control-label">客户端ID</label>
|
||||
<input class="form-control" value="{{.t.ClientId}}" type="text" name="client_id"
|
||||
placeholder="客户端id">
|
||||
</div>
|
||||
<div class="form-group" id="use_client">
|
||||
<label class="control-label">是否使用客户端配置</label>
|
||||
<select class="form-control" name="use_client">
|
||||
<option {{if eq false .t.UseClientCnf}}selected{{end}} value="0">否</option>
|
||||
<option {{if eq true .t.UseClientCnf}}selected{{end}} value="1">是</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<label class="control-label">数据压缩方式(所有模式均支持)</label>
|
||||
<select class="form-control" name="compress">
|
||||
|
@ -32,14 +48,14 @@
|
|||
<option {{if eq "snappy" .t.Compress}}selected{{end}} value="snappy">snappy压缩</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<div class="form-group" id="crypt">
|
||||
<label class="control-label">是否加密传输(所有模式均支持)</label>
|
||||
<select class="form-control" name="crypt">
|
||||
<option {{if eq false .t.Crypt}}selected{{end}} value="0">不加密</option>
|
||||
<option {{if eq true .t.Crypt}}selected{{end}} value="1">加密</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="compress">
|
||||
<div class="form-group" id="mux">
|
||||
<label class="control-label">是否启用TCP复用(所有模式均支持)</label>
|
||||
<select class="form-control" name="mux">
|
||||
<option {{if eq false .t.Mux}}selected{{end}} value="0">不启用</option>
|
||||
|
@ -74,7 +90,7 @@
|
|||
arr["udpServer"] = ["type", "port", "target", "compress"]
|
||||
arr["socks5Server"] = ["type", "port", "compress", "u", "p"]
|
||||
arr["httpProxyServer"] = ["type", "port", "compress", "u", "p"]
|
||||
arr["hostServer"] = ["type", "compress", "u", "p"]
|
||||
arrClientHide = ["compress", "u", "p", "crypt", "mux"]
|
||||
|
||||
function resetForm() {
|
||||
for (var i = 0; i < arr["all"].length; i++) {
|
||||
|
@ -86,10 +102,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
function resetClientCnf() {
|
||||
for (var i = 0; i < arrClientHide.length; i++) {
|
||||
$("#" + arrClientHide[i]).css("display", "block")
|
||||
}
|
||||
op = $("#use_client option:selected").val()
|
||||
if (op == 1) {
|
||||
for (var i = 0; i < arrClientHide.length; i++) {
|
||||
$("#" + arrClientHide[i]).css("display", "none")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
resetForm()
|
||||
resetClientCnf()
|
||||
$("#type").on("change", function () {
|
||||
resetForm()
|
||||
resetClientCnf()
|
||||
})
|
||||
$("#use_client").on("change", function () {
|
||||
resetForm()
|
||||
resetClientCnf()
|
||||
})
|
||||
$("#add").on("click", function () {
|
||||
$.ajax({
|
||||
|
|
|
@ -4,18 +4,26 @@
|
|||
<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="remark" placeholder="备注">
|
||||
</div>
|
||||
<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">客户端id</label>
|
||||
<input value="{{.client_id}}" class="form-control" type="text" name="client_id" placeholder="客户端id">
|
||||
</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>
|
||||
<div class="form-group" id="header">
|
||||
<label class="control-label">header头修改(冒号分隔,多个请换行填写)</label>
|
||||
<textarea class="form-control" rows="4" type="text" name="header" placeholder="Cache-Control: no-cache"></textarea>
|
||||
<textarea class="form-control" rows="4" type="text" name="header"
|
||||
placeholder="Cache-Control: no-cache"></textarea>
|
||||
</div>
|
||||
<div class="form-group" id="hostchange">
|
||||
<label class="control-label">host修改</label>
|
||||
|
|
|
@ -4,12 +4,20 @@
|
|||
<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>
|
||||
<input class="form-control" value="{{.h.Host}}" type="hidden" name="host" placeholder="域名">
|
||||
<input class="form-control" value="{{.h.Host}}" type="text" name="nhost" placeholder="域名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">备注</label>
|
||||
<input class="form-control" value="{{.h.Remark}}" type="text" name="remark" placeholder="备注">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">客户端id</label>
|
||||
<input class="form-control" value="{{.h.ClientId}}" type="text" name="client_id"
|
||||
placeholder="客户端id">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">内网目标</label>
|
||||
<input class="form-control" value="{{.h.Target}}" type="text" name="target"
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<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配置中添加反向代理(或直接将http监听端口改为80)<br>
|
||||
<pre><code>
|
||||
server {
|
||||
listen 80;
|
||||
server_name a.proxy.com b.proxy.com;#也可以是泛解析*.proxy.com
|
||||
#ssl等配置
|
||||
<b>location / {
|
||||
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>服务端启动:./proxy_server</li>
|
||||
<li>客户端启动:./proxy_client -server={{.ip}}:{{.p}} -vkey=xxx(见管理列表的客户端启动模式)</li>
|
||||
</p>
|
||||
<p><b>支持客户端同时建立多条隧道,例如单个通道时命令为./proxy_client -server={{.ip}}:{{.p}}
|
||||
-vkey=ccc,如果要支持另外一个隧道,则对应的执行命令为./proxy_client
|
||||
-server={{.ip}}:{{.p}} -vkey=ccc,ddd,即用逗号分隔开多个vkey,适用于所有模式!</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -5,9 +5,13 @@
|
|||
<table class="table table-hover table-bordered" id="sampleTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>客户端key</th>
|
||||
<th>客户端id</th>
|
||||
<th>备注</th>
|
||||
<th>host</th>
|
||||
<th>内网目标</th>
|
||||
<th>host改写</th>
|
||||
<th>出口流量</th>
|
||||
<th>入口流量</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -39,7 +43,7 @@
|
|||
}
|
||||
|
||||
function add() {
|
||||
window.location.href = "/index/addhost?vkey={{.vkey}}"
|
||||
window.location.href = "/index/addhost?vkey={{.task_id}}&client_id={{.client_id}}"
|
||||
}
|
||||
|
||||
function edit(host) {
|
||||
|
@ -48,6 +52,11 @@
|
|||
|
||||
$(document).ready(function () {
|
||||
var table = $('#sampleTable').DataTable({
|
||||
responsive: {
|
||||
details: {
|
||||
display: $.fn.dataTable.Responsive.display.childRowImmediate
|
||||
}
|
||||
},
|
||||
dom: 'Bfrtip',
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
|
@ -59,9 +68,13 @@
|
|||
},
|
||||
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
|
||||
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
|
||||
{data: 'Vkey'},
|
||||
{data: 'ClientId'},
|
||||
{data: 'Remark'},
|
||||
{data: 'Host'},
|
||||
{data: 'Target'},
|
||||
{data: 'HostChange'},
|
||||
{data: 'HostChange'},
|
||||
{data: 'HostChange'},
|
||||
{data: 'Target'},
|
||||
],
|
||||
bFilter: false,
|
||||
|
@ -74,7 +87,19 @@
|
|||
'<button onclick="edit(\'' + row.Host + '\')" type="button" class="btn btn-primary btn-sm">编辑查看</button> '
|
||||
+ ' </div>'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -2,
|
||||
render: function (data, type, row, meta) {
|
||||
return change(row.Flow.InletFlow)
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -3,
|
||||
render: function (data, type, row, meta) {
|
||||
return change(row.Flow.ExportFlow)
|
||||
}
|
||||
}
|
||||
],
|
||||
buttons: []
|
||||
});
|
||||
|
|
|
@ -1,147 +1,104 @@
|
|||
<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 class="col-md-3">
|
||||
<div class="widget-small warning coloured-icon"><i class="icon fa fa-html5 fa-3x"></i>
|
||||
<div class="info">
|
||||
<h4>HTTP端口</h4>
|
||||
<p><b>{{.p}}</b></p>
|
||||
</div>
|
||||
</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配置中添加反向代理(或直接将http监听端口改为80)<br>
|
||||
<pre><code>
|
||||
server {
|
||||
listen 80;
|
||||
server_name a.proxy.com b.proxy.com;#也可以是泛解析*.proxy.com
|
||||
#ssl等配置
|
||||
<b>location / {
|
||||
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 class="col-md-3">
|
||||
<div class="widget-small danger coloured-icon"><i class="icon fa fa-home fa-3x"></i>
|
||||
<div class="info">
|
||||
<h4>域名解析数</h4>
|
||||
<p><b>{{.data.hostCount}}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="widget-small primary coloured-icon"><i class="icon fa fa-users fa-3x"></i>
|
||||
<div class="info">
|
||||
<h4>总客户端数</h4>
|
||||
<p><b>{{.data.clientCount}}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="widget-small info coloured-icon"><i class="icon fa fa-user-secret fa-3x"></i>
|
||||
<div class="info">
|
||||
<h4>在线客户端数</h4>
|
||||
<p><b>{{.data.clientOnlineCount}}</b></p>
|
||||
</div>
|
||||
</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>
|
||||
<h3 class="tile-title">流量</h3>
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<canvas class="embed-responsive-item" id="flow"></canvas>
|
||||
</div>
|
||||
</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>
|
||||
<h3 class="tile-title">代理类型</h3>
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<canvas class="embed-responsive-item" id="types"></canvas>
|
||||
</div>
|
||||
</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>服务端启动:./proxy_server</li>
|
||||
<li>客户端启动:./proxy_client -server={{.ip}}:{{.p}} -vkey=xxx(见管理列表的客户端启动模式)</li>
|
||||
</p>
|
||||
<p><b>支持客户端同时建立多条隧道,例如单个通道时命令为./proxy_client -server={{.ip}}:{{.p}} -vkey=ccc,如果要支持另外一个隧道,则对应的执行命令为./proxy_client
|
||||
-server={{.ip}}:{{.p}} -vkey=ccc,ddd,即用逗号分隔开多个vkey,适用于所有模式!</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<script>
|
||||
var pdataFlow = [
|
||||
{
|
||||
value: {{.data.inletFlowCount}},
|
||||
color: "#46BFBD",
|
||||
highlight: "#5AD3D1",
|
||||
label: "入口流量"
|
||||
},
|
||||
{
|
||||
value: {{.data.exportFlowCount}},
|
||||
color: "#FDB45C",
|
||||
highlight: "#FFC870",
|
||||
label: "出口流量"
|
||||
}
|
||||
]
|
||||
var pdataTypes = [
|
||||
{
|
||||
value: {{.data.tunnelServerCount}},
|
||||
color: "#46BFBD",
|
||||
highlight: "#5AD3D1",
|
||||
label: "tcp隧道"
|
||||
},
|
||||
{
|
||||
value: {{.data.socks5ServerCount}},
|
||||
color: "#85FEAA",
|
||||
highlight: "#85FEAA",
|
||||
label: "socks5隧道"
|
||||
},
|
||||
{
|
||||
value: {{.data.httpProxyServerCount}},
|
||||
color: "#4B653C",
|
||||
highlight: "#4B653C",
|
||||
label: "http代理"
|
||||
},
|
||||
{
|
||||
value: {{.data.udpServerCount}},
|
||||
color: "#90653C",
|
||||
highlight: "#90653C",
|
||||
label: "udp代理"
|
||||
},
|
||||
{
|
||||
value: {{.data.hostCount}},
|
||||
color: "#FDB45C",
|
||||
highlight: "#FDB45C",
|
||||
label: "域名解析"
|
||||
}
|
||||
]
|
||||
var ctxp = $("#flow").get(0).getContext("2d");
|
||||
var pieChart = new Chart(ctxp).Pie(pdataFlow);
|
||||
var ctxd = $("#types").get(0).getContext("2d");
|
||||
var doughnutChart = new Chart(ctxd).Doughnut(pdataTypes);
|
||||
</script>
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
<table class="table table-hover table-bordered" id="sampleTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{{/*<th>模式</th>*/}}
|
||||
<th>Id</th>
|
||||
<th>备注</th>
|
||||
<th>客户端Id</th>
|
||||
<th>监听端口</th>
|
||||
<th>内网目标</th>
|
||||
<th>多客户端模式客户端执行命令</th>
|
||||
<th>压缩方式</th>
|
||||
<th>加密传输</th>
|
||||
<th>TCP多路复用</th>
|
||||
|
@ -16,11 +17,12 @@
|
|||
<th>密码</th>
|
||||
<th>客户端状态</th>
|
||||
<th>状态</th>
|
||||
<th>出口流量</th>
|
||||
<th>入口流量</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -30,12 +32,12 @@
|
|||
</main>
|
||||
|
||||
<script type="text/javascript">
|
||||
function del(vKey) {
|
||||
function del(id) {
|
||||
if (confirm("确定要删除数据吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/del",
|
||||
data: {"vKey": vKey},
|
||||
data: {"id": id},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
|
@ -46,12 +48,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
function start(vKey) {
|
||||
function start(id) {
|
||||
if (confirm("确定要开始任务吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/start",
|
||||
data: {"vKey": vKey},
|
||||
data: {"id": id},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
|
@ -62,12 +64,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
function stop(vKey) {
|
||||
function stop(id) {
|
||||
if (confirm("确定要暂停吗?")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/stop",
|
||||
data: {"vKey": vKey},
|
||||
data: {"id": id},
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
|
@ -78,35 +80,38 @@
|
|||
}
|
||||
}
|
||||
|
||||
function edit(vKey) {
|
||||
window.location.href = "/index/edit?vKey=" + vKey
|
||||
function edit(id) {
|
||||
window.location.href = "/index/edit?id=" + id
|
||||
}
|
||||
|
||||
function add() {
|
||||
window.location.href = "/index/add?type=" +{{.type}}
|
||||
window.location.href = "/index/add?type={{.type}}" + "&client_id={{.client_id}}"
|
||||
}
|
||||
|
||||
function hostList(vkey) {
|
||||
window.location.href = "/index/hostlist?vkey=" + vkey
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var table = $('#sampleTable').DataTable({
|
||||
responsive: {
|
||||
details: {
|
||||
display: $.fn.dataTable.Responsive.display.childRowImmediate
|
||||
}
|
||||
},
|
||||
dom: 'Bfrtip',
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
autoWidth: false,
|
||||
ordering: false,
|
||||
ajax: {
|
||||
url: '/index/getserverconfig?type={{.type}}',
|
||||
url: '/index/getserverconfig?type={{.type}}' + "&client_id=" +{{.client_id}},
|
||||
type: 'POST'
|
||||
},
|
||||
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
|
||||
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
|
||||
// {data: 'Mode'},
|
||||
{data: 'Id'},
|
||||
{data: 'Remark'},
|
||||
{data: 'ClientId'},
|
||||
{data: 'TcpPort'},
|
||||
{data: 'Target'},
|
||||
{data: 'VerifyKey'},
|
||||
{data: 'Compress'},
|
||||
{data: 'Crypt'},
|
||||
{data: 'Mux'},
|
||||
|
@ -114,6 +119,8 @@
|
|||
{data: 'P'},
|
||||
{data: 'ClientStatus'},
|
||||
{data: 'IsRun'},
|
||||
{data: 'ExportFlow'},
|
||||
{data: 'InletFlow'},
|
||||
{data: "Id"}
|
||||
],
|
||||
bFilter: false,
|
||||
|
@ -121,27 +128,18 @@
|
|||
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>"
|
||||
btn = "<button onclick=\"stop('" + row.Id + "')\" 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 = ""
|
||||
btn = "<button onclick=\"start('" + row.Id + "')\" class=\"btn btn-success btn-sm\" type=\"button\">打开</button>"
|
||||
}
|
||||
btn_edit = '<button onclick="edit(\'' + row.Id + '\')" type="button" class="btn btn-primary btn-sm">查看编辑</button> '
|
||||
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>'
|
||||
'<button onclick="del(\'' + row.Id + '\')" type="button" class="btn btn-danger btn-sm">删除</button>' +
|
||||
btn_edit + btn + ' </div>'
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -2,
|
||||
targets: -4,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == 0) {
|
||||
return "<span class=\"badge badge-pill badge-secondary\">暂停</span>"
|
||||
|
@ -151,7 +149,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
targets: -7,
|
||||
targets: -9,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == "0") {
|
||||
return "不加密"
|
||||
|
@ -161,7 +159,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
targets: -6,
|
||||
targets: -8,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == "0") {
|
||||
return "不启用"
|
||||
|
@ -169,17 +167,9 @@
|
|||
return "启用"
|
||||
}
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
targets: 2,
|
||||
render: function (data, type, row, meta) {
|
||||
return "./proxy_client -server={{.ip}}:{{.p}} -vkey=" + data
|
||||
// return data
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -3,
|
||||
targets: -5,
|
||||
render: function (data, type, row, meta) {
|
||||
if (data == 0) {
|
||||
return "<span class=\"badge badge-pill badge-secondary\">离线</span>"
|
||||
|
@ -187,6 +177,18 @@
|
|||
return "<span class=\"badge badge-pill badge-success\">在线</span>"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -2,
|
||||
render: function (data, type, row, meta) {
|
||||
return change(row.Flow.InletFlow)
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: -3,
|
||||
render: function (data, type, row, meta) {
|
||||
return change(row.Flow.ExportFlow)
|
||||
}
|
||||
}
|
||||
],
|
||||
buttons: []
|
||||
|
|
|
@ -41,8 +41,10 @@
|
|||
</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-dashboard"></i><span class="app-menu__label">dashboard</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "client" .menu}}active{{end}}" href="/client/client"><i
|
||||
class="app-menu__icon fa fa-dot-circle-o"></i><span class="app-menu__label">客户端管理</span></a></li>
|
||||
<li><a class="app-menu__item {{if eq "host" .menu}}active{{end}}" href="/index/hostlist"><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>
|
||||
|
@ -52,12 +54,14 @@
|
|||
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>
|
||||
|
||||
<li><a class="app-menu__item {{if eq "help" .menu}}active{{end}}" href="/index/help"><i
|
||||
class="app-menu__icon fa fa-question-circle"></i><span class="app-menu__label">使用说明</span></a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<script type="text/javascript" src="/static/js/datatables.min.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script src="/static/js/chart.js"></script>
|
||||
<main class="app-content">
|
||||
<div class="app-title">
|
||||
<div>
|
||||
|
|
Loading…
Reference in New Issue