remove old file
536
bridge/bridge.go
@ -1,536 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"ehang.io/nps-mux"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/lib/version"
|
||||
"ehang.io/nps/server/connection"
|
||||
"ehang.io/nps/server/tool"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
tunnel *nps_mux.Mux
|
||||
signal *conn.Conn
|
||||
file *nps_mux.Mux
|
||||
Version string
|
||||
retryTime int // it will be add 1 when ping not ok until to 3 will close the client
|
||||
}
|
||||
|
||||
func NewClient(t, f *nps_mux.Mux, s *conn.Conn, vs string) *Client {
|
||||
return &Client{
|
||||
signal: s,
|
||||
tunnel: t,
|
||||
file: f,
|
||||
Version: vs,
|
||||
}
|
||||
}
|
||||
|
||||
type Bridge struct {
|
||||
TunnelPort int //通信隧道端口
|
||||
Client sync.Map
|
||||
Register sync.Map
|
||||
tunnelType string //bridge type kcp or tcp
|
||||
OpenTask chan *file.Tunnel
|
||||
CloseTask chan *file.Tunnel
|
||||
CloseClient chan int
|
||||
SecretChan chan *conn.Secret
|
||||
ipVerify bool
|
||||
runList sync.Map //map[int]interface{}
|
||||
disconnectTime int
|
||||
}
|
||||
|
||||
func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList sync.Map, disconnectTime int) *Bridge {
|
||||
return &Bridge{
|
||||
TunnelPort: tunnelPort,
|
||||
tunnelType: tunnelType,
|
||||
OpenTask: make(chan *file.Tunnel),
|
||||
CloseTask: make(chan *file.Tunnel),
|
||||
CloseClient: make(chan int),
|
||||
SecretChan: make(chan *conn.Secret),
|
||||
ipVerify: ipVerify,
|
||||
runList: runList,
|
||||
disconnectTime: disconnectTime,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Bridge) StartTunnel() error {
|
||||
go s.ping()
|
||||
if s.tunnelType == "kcp" {
|
||||
logs.Info("server start, the bridge type is %s, the bridge port is %d", s.tunnelType, s.TunnelPort)
|
||||
return conn.NewKcpListenerAndProcess(beego.AppConfig.String("bridge_ip")+":"+beego.AppConfig.String("bridge_port"), func(c net.Conn) {
|
||||
s.cliProcess(conn.NewConn(c))
|
||||
})
|
||||
} else {
|
||||
listener, err := connection.GetBridgeListener(s.tunnelType)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
return err
|
||||
}
|
||||
conn.Accept(listener, func(c net.Conn) {
|
||||
s.cliProcess(conn.NewConn(c))
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//get health information form client
|
||||
func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {
|
||||
for {
|
||||
if info, status, err := c.GetHealthInfo(); err != nil {
|
||||
break
|
||||
} else if !status { //the status is true , return target to the targetArr
|
||||
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Tunnel)
|
||||
if v.Client.Id == id && v.Mode == "tcp" && strings.Contains(v.Target.TargetStr, info) {
|
||||
v.Lock()
|
||||
if v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
|
||||
v.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, "\n"))
|
||||
}
|
||||
v.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info)
|
||||
if v.HealthRemoveArr == nil {
|
||||
v.HealthRemoveArr = make([]string, 0)
|
||||
}
|
||||
v.HealthRemoveArr = append(v.HealthRemoveArr, info)
|
||||
v.Unlock()
|
||||
}
|
||||
return true
|
||||
})
|
||||
file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Host)
|
||||
if v.Client.Id == id && strings.Contains(v.Target.TargetStr, info) {
|
||||
v.Lock()
|
||||
if v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
|
||||
v.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, "\n"))
|
||||
}
|
||||
v.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info)
|
||||
if v.HealthRemoveArr == nil {
|
||||
v.HealthRemoveArr = make([]string, 0)
|
||||
}
|
||||
v.HealthRemoveArr = append(v.HealthRemoveArr, info)
|
||||
v.Unlock()
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else { //the status is false,remove target from the targetArr
|
||||
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Tunnel)
|
||||
if v.Client.Id == id && v.Mode == "tcp" && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) {
|
||||
v.Lock()
|
||||
v.Target.TargetArr = append(v.Target.TargetArr, info)
|
||||
v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)
|
||||
v.Unlock()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Host)
|
||||
if v.Client.Id == id && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) {
|
||||
v.Lock()
|
||||
v.Target.TargetArr = append(v.Target.TargetArr, info)
|
||||
v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)
|
||||
v.Unlock()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
s.DelClient(id)
|
||||
}
|
||||
|
||||
//验证失败,返回错误验证flag,并且关闭连接
|
||||
func (s *Bridge) verifyError(c *conn.Conn) {
|
||||
c.Write([]byte(common.VERIFY_EER))
|
||||
}
|
||||
|
||||
func (s *Bridge) verifySuccess(c *conn.Conn) {
|
||||
c.Write([]byte(common.VERIFY_SUCCESS))
|
||||
}
|
||||
|
||||
func (s *Bridge) cliProcess(c *conn.Conn) {
|
||||
//read test flag
|
||||
if _, err := c.GetShortContent(3); err != nil {
|
||||
logs.Info("The client %s connect error", c.Conn.RemoteAddr(), err.Error())
|
||||
return
|
||||
}
|
||||
//version check
|
||||
if b, err := c.GetShortLenContent(); err != nil || string(b) != version.GetVersion() {
|
||||
logs.Info("The client %s version does not match", c.Conn.RemoteAddr())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
//version get
|
||||
var vs []byte
|
||||
var err error
|
||||
if vs, err = c.GetShortLenContent(); err != nil {
|
||||
logs.Info("get client %s version error", err.Error())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
//write server version to client
|
||||
c.Write([]byte(crypt.Md5(version.GetVersion())))
|
||||
c.SetReadDeadlineBySecond(5)
|
||||
var buf []byte
|
||||
//get vKey from client
|
||||
if buf, err = c.GetShortContent(32); err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
//verify
|
||||
id, err := file.GetDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
logs.Info("Current client connection validation error, close this client:", c.Conn.RemoteAddr())
|
||||
s.verifyError(c)
|
||||
return
|
||||
} else {
|
||||
s.verifySuccess(c)
|
||||
}
|
||||
if flag, err := c.ReadFlag(); err == nil {
|
||||
s.typeDeal(flag, c, id, string(vs))
|
||||
} else {
|
||||
logs.Warn(err, flag)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Bridge) DelClient(id int) {
|
||||
if v, ok := s.Client.Load(id); ok {
|
||||
if v.(*Client).signal != nil {
|
||||
v.(*Client).signal.Close()
|
||||
}
|
||||
s.Client.Delete(id)
|
||||
if file.GetDb().IsPubClient(id) {
|
||||
return
|
||||
}
|
||||
if c, err := file.GetDb().GetClient(id); err == nil {
|
||||
s.CloseClient <- c.Id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//use different
|
||||
func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {
|
||||
isPub := file.GetDb().IsPubClient(id)
|
||||
switch typeVal {
|
||||
case common.WORK_MAIN:
|
||||
if isPub {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
tcpConn, ok := c.Conn.(*net.TCPConn)
|
||||
if ok {
|
||||
// add tcp keep alive option for signal connection
|
||||
_ = tcpConn.SetKeepAlive(true)
|
||||
_ = tcpConn.SetKeepAlivePeriod(5 * time.Second)
|
||||
}
|
||||
//the vKey connect by another ,close the client of before
|
||||
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c, vs)); ok {
|
||||
if v.(*Client).signal != nil {
|
||||
v.(*Client).signal.WriteClose()
|
||||
}
|
||||
v.(*Client).signal = c
|
||||
v.(*Client).Version = vs
|
||||
}
|
||||
go s.GetHealthFromClient(id, c)
|
||||
logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
|
||||
case common.WORK_CHAN:
|
||||
muxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime)
|
||||
if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil, vs)); ok {
|
||||
v.(*Client).tunnel = muxConn
|
||||
}
|
||||
case common.WORK_CONFIG:
|
||||
client, err := file.GetDb().GetClient(id)
|
||||
if err != nil || (!isPub && !client.ConfigConnAllow) {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
binary.Write(c, binary.LittleEndian, isPub)
|
||||
go s.getConfig(c, isPub, client)
|
||||
case common.WORK_REGISTER:
|
||||
go s.register(c)
|
||||
case common.WORK_SECRET:
|
||||
if b, err := c.GetShortContent(32); err == nil {
|
||||
s.SecretChan <- conn.NewSecret(string(b), c)
|
||||
} else {
|
||||
logs.Error("secret error, failed to match the key successfully")
|
||||
}
|
||||
case common.WORK_FILE:
|
||||
muxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime)
|
||||
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil, vs)); ok {
|
||||
v.(*Client).file = muxConn
|
||||
}
|
||||
case common.WORK_P2P:
|
||||
//read md5 secret
|
||||
if b, err := c.GetShortContent(32); err != nil {
|
||||
logs.Error("p2p error,", err.Error())
|
||||
} else if t := file.GetDb().GetTaskByMd5Password(string(b)); t == nil {
|
||||
logs.Error("p2p error, failed to match the key successfully")
|
||||
} else {
|
||||
if v, ok := s.Client.Load(t.Client.Id); !ok {
|
||||
return
|
||||
} else {
|
||||
//向密钥对应的客户端发送与服务端udp建立连接信息,地址,密钥
|
||||
v.(*Client).signal.Write([]byte(common.NEW_UDP_CONN))
|
||||
svrAddr := beego.AppConfig.String("p2p_ip") + ":" + beego.AppConfig.String("p2p_port")
|
||||
if err != nil {
|
||||
logs.Warn("get local udp addr error")
|
||||
return
|
||||
}
|
||||
v.(*Client).signal.WriteLenContent([]byte(svrAddr))
|
||||
v.(*Client).signal.WriteLenContent(b)
|
||||
//向该请求者发送建立连接请求,服务器地址
|
||||
c.WriteLenContent([]byte(svrAddr))
|
||||
}
|
||||
}
|
||||
}
|
||||
c.SetAlive(s.tunnelType)
|
||||
return
|
||||
}
|
||||
|
||||
//register ip
|
||||
func (s *Bridge) register(c *conn.Conn) {
|
||||
var hour int32
|
||||
if err := binary.Read(c, binary.LittleEndian, &hour); err == nil {
|
||||
s.Register.Store(common.GetIpByAddr(c.Conn.RemoteAddr().String()), time.Now().Add(time.Hour*time.Duration(hour)))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) {
|
||||
//if the proxy type is local
|
||||
if link.LocalProxy {
|
||||
target, err = net.Dial("tcp", link.Host)
|
||||
return
|
||||
}
|
||||
if v, ok := s.Client.Load(clientId); ok {
|
||||
//If ip is restricted to do ip verification
|
||||
if s.ipVerify {
|
||||
ip := common.GetIpByAddr(link.RemoteAddr)
|
||||
if v, ok := s.Register.Load(ip); !ok {
|
||||
return nil, errors.New(fmt.Sprintf("The ip %s is not in the validation list", ip))
|
||||
} else {
|
||||
if !v.(time.Time).After(time.Now()) {
|
||||
return nil, errors.New(fmt.Sprintf("The validity of the ip %s has expired", ip))
|
||||
}
|
||||
}
|
||||
}
|
||||
var tunnel *nps_mux.Mux
|
||||
if t != nil && t.Mode == "file" {
|
||||
tunnel = v.(*Client).file
|
||||
} else {
|
||||
tunnel = v.(*Client).tunnel
|
||||
}
|
||||
if tunnel == nil {
|
||||
err = errors.New("the client connect error")
|
||||
return
|
||||
}
|
||||
if target, err = tunnel.NewConn(); err != nil {
|
||||
return
|
||||
}
|
||||
if t != nil && t.Mode == "file" {
|
||||
//TODO if t.mode is file ,not use crypt or compress
|
||||
link.Crypt = false
|
||||
link.Compress = false
|
||||
return
|
||||
}
|
||||
if _, err = conn.NewConn(target).SendInfo(link, ""); err != nil {
|
||||
logs.Info("new connect error ,the target %s refuse to connect", link.Host)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = errors.New(fmt.Sprintf("the client %d is not connect", clientId))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Bridge) ping() {
|
||||
ticker := time.NewTicker(time.Second * 5)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
arr := make([]int, 0)
|
||||
s.Client.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Client)
|
||||
if v.tunnel == nil || v.signal == nil {
|
||||
v.retryTime += 1
|
||||
if v.retryTime >= 3 {
|
||||
arr = append(arr, key.(int))
|
||||
}
|
||||
return true
|
||||
}
|
||||
if v.tunnel.IsClose {
|
||||
arr = append(arr, key.(int))
|
||||
}
|
||||
return true
|
||||
})
|
||||
for _, v := range arr {
|
||||
logs.Info("the client %d closed", v)
|
||||
s.DelClient(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//get config and add task from client config
|
||||
func (s *Bridge) getConfig(c *conn.Conn, isPub bool, client *file.Client) {
|
||||
var fail bool
|
||||
loop:
|
||||
for {
|
||||
flag, err := c.ReadFlag()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch flag {
|
||||
case common.WORK_STATUS:
|
||||
if b, err := c.GetShortContent(32); err != nil {
|
||||
break loop
|
||||
} else {
|
||||
var str string
|
||||
id, err := file.GetDb().GetClientIdByVkey(string(b))
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Host)
|
||||
if v.Client.Id == id {
|
||||
str += v.Remark + common.CONN_DATA_SEQ
|
||||
}
|
||||
return true
|
||||
})
|
||||
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Tunnel)
|
||||
//if _, ok := s.runList[v.Id]; ok && v.Client.Id == id {
|
||||
if _, ok := s.runList.Load(v.Id); ok && v.Client.Id == id {
|
||||
str += v.Remark + common.CONN_DATA_SEQ
|
||||
}
|
||||
return true
|
||||
})
|
||||
binary.Write(c, binary.LittleEndian, int32(len([]byte(str))))
|
||||
binary.Write(c, binary.LittleEndian, []byte(str))
|
||||
}
|
||||
case common.NEW_CONF:
|
||||
var err error
|
||||
if client, err = c.GetConfigInfo(); err != nil {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
} else {
|
||||
if err = file.GetDb().NewClient(client); err != nil {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
}
|
||||
c.WriteAddOk()
|
||||
c.Write([]byte(client.VerifyKey))
|
||||
s.Client.Store(client.Id, NewClient(nil, nil, nil, ""))
|
||||
}
|
||||
case common.NEW_HOST:
|
||||
h, err := c.GetHostInfo()
|
||||
if err != nil {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
}
|
||||
h.Client = client
|
||||
if h.Location == "" {
|
||||
h.Location = "/"
|
||||
}
|
||||
if !client.HasHost(h) {
|
||||
if file.GetDb().IsHostExist(h) {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
} else {
|
||||
file.GetDb().NewHost(h)
|
||||
c.WriteAddOk()
|
||||
}
|
||||
} else {
|
||||
c.WriteAddOk()
|
||||
}
|
||||
case common.NEW_TASK:
|
||||
if t, err := c.GetTaskInfo(); err != nil {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
} else {
|
||||
ports := common.GetPorts(t.Ports)
|
||||
targets := common.GetPorts(t.Target.TargetStr)
|
||||
if len(ports) > 1 && (t.Mode == "tcp" || t.Mode == "udp") && (len(ports) != len(targets)) {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
} else if t.Mode == "secret" || t.Mode == "p2p" {
|
||||
ports = append(ports, 0)
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
}
|
||||
for i := 0; i < len(ports); i++ {
|
||||
tl := new(file.Tunnel)
|
||||
tl.Mode = t.Mode
|
||||
tl.Port = ports[i]
|
||||
tl.ServerIp = t.ServerIp
|
||||
if len(ports) == 1 {
|
||||
tl.Target = t.Target
|
||||
tl.Remark = t.Remark
|
||||
} else {
|
||||
tl.Remark = t.Remark + "_" + strconv.Itoa(tl.Port)
|
||||
tl.Target = new(file.Target)
|
||||
if t.TargetAddr != "" {
|
||||
tl.Target.TargetStr = t.TargetAddr + ":" + strconv.Itoa(targets[i])
|
||||
} else {
|
||||
tl.Target.TargetStr = strconv.Itoa(targets[i])
|
||||
}
|
||||
}
|
||||
tl.Id = int(file.GetDb().JsonDb.GetTaskId())
|
||||
tl.Status = true
|
||||
tl.Flow = new(file.Flow)
|
||||
tl.NoStore = true
|
||||
tl.Client = client
|
||||
tl.Password = t.Password
|
||||
tl.LocalPath = t.LocalPath
|
||||
tl.StripPre = t.StripPre
|
||||
tl.MultiAccount = t.MultiAccount
|
||||
if !client.HasTunnel(tl) {
|
||||
if err := file.GetDb().NewTask(tl); err != nil {
|
||||
logs.Notice("Add task error ", err.Error())
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
}
|
||||
if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secret" && t.Mode != "p2p" {
|
||||
fail = true
|
||||
c.WriteAddFail()
|
||||
break loop
|
||||
} else {
|
||||
s.OpenTask <- tl
|
||||
}
|
||||
}
|
||||
c.WriteAddOk()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if fail && client != nil {
|
||||
s.DelClient(client.Id)
|
||||
}
|
||||
c.Close()
|
||||
}
|
311
client/client.go
@ -1,311 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"ehang.io/nps-mux"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/xtaci/kcp-go"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/config"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
)
|
||||
|
||||
type TRPClient struct {
|
||||
svrAddr string
|
||||
bridgeConnType string
|
||||
proxyUrl string
|
||||
vKey string
|
||||
p2pAddr map[string]string
|
||||
tunnel *nps_mux.Mux
|
||||
signal *conn.Conn
|
||||
ticker *time.Ticker
|
||||
cnf *config.Config
|
||||
disconnectTime int
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
//new client
|
||||
func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl string, cnf *config.Config, disconnectTime int) *TRPClient {
|
||||
return &TRPClient{
|
||||
svrAddr: svraddr,
|
||||
p2pAddr: make(map[string]string, 0),
|
||||
vKey: vKey,
|
||||
bridgeConnType: bridgeConnType,
|
||||
proxyUrl: proxyUrl,
|
||||
cnf: cnf,
|
||||
disconnectTime: disconnectTime,
|
||||
once: sync.Once{},
|
||||
}
|
||||
}
|
||||
|
||||
var NowStatus int
|
||||
var CloseClient bool
|
||||
|
||||
//start
|
||||
func (s *TRPClient) Start() {
|
||||
CloseClient = false
|
||||
retry:
|
||||
if CloseClient {
|
||||
return
|
||||
}
|
||||
NowStatus = 0
|
||||
c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl)
|
||||
if err != nil {
|
||||
logs.Error("The connection server failed and will be reconnected in five seconds, error", err.Error())
|
||||
time.Sleep(time.Second * 5)
|
||||
goto retry
|
||||
}
|
||||
if c == nil {
|
||||
logs.Error("Error data from server, and will be reconnected in five seconds")
|
||||
time.Sleep(time.Second * 5)
|
||||
goto retry
|
||||
}
|
||||
logs.Info("Successful connection with server %s", s.svrAddr)
|
||||
//monitor the connection
|
||||
go s.ping()
|
||||
s.signal = c
|
||||
//start a channel connection
|
||||
go s.newChan()
|
||||
//start health check if the it's open
|
||||
if s.cnf != nil && len(s.cnf.Healths) > 0 {
|
||||
go heathCheck(s.cnf.Healths, s.signal)
|
||||
}
|
||||
NowStatus = 1
|
||||
//msg connection, eg udp
|
||||
s.handleMain()
|
||||
}
|
||||
|
||||
//handle main connection
|
||||
func (s *TRPClient) handleMain() {
|
||||
for {
|
||||
flags, err := s.signal.ReadFlag()
|
||||
if err != nil {
|
||||
logs.Error("Accept server data error %s, end this service", err.Error())
|
||||
break
|
||||
}
|
||||
switch flags {
|
||||
case common.NEW_UDP_CONN:
|
||||
//read server udp addr and password
|
||||
if lAddr, err := s.signal.GetShortLenContent(); err != nil {
|
||||
logs.Warn(err)
|
||||
return
|
||||
} else if pwd, err := s.signal.GetShortLenContent(); err == nil {
|
||||
var localAddr string
|
||||
//The local port remains unchanged for a certain period of time
|
||||
if v, ok := s.p2pAddr[crypt.Md5(string(pwd)+strconv.Itoa(int(time.Now().Unix()/100)))]; !ok {
|
||||
tmpConn, err := common.GetLocalUdpAddr()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
localAddr = tmpConn.LocalAddr().String()
|
||||
} else {
|
||||
localAddr = v
|
||||
}
|
||||
go s.newUdpConn(localAddr, string(lAddr), string(pwd))
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Close()
|
||||
}
|
||||
|
||||
func (s *TRPClient) newUdpConn(localAddr, rAddr string, md5Password string) {
|
||||
var localConn net.PacketConn
|
||||
var err error
|
||||
var remoteAddress string
|
||||
if remoteAddress, localConn, err = handleP2PUdp(localAddr, rAddr, md5Password, common.WORK_P2P_PROVIDER); err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
l, err := kcp.ServeConn(nil, 150, 3, localConn)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
logs.Trace("start local p2p udp listen, local address", localConn.LocalAddr().String())
|
||||
for {
|
||||
udpTunnel, err := l.AcceptKCP()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
l.Close()
|
||||
return
|
||||
}
|
||||
if udpTunnel.RemoteAddr().String() == string(remoteAddress) {
|
||||
conn.SetUdpSession(udpTunnel)
|
||||
logs.Trace("successful connection with client ,address %s", udpTunnel.RemoteAddr().String())
|
||||
//read link info from remote
|
||||
conn.Accept(nps_mux.NewMux(udpTunnel, s.bridgeConnType, s.disconnectTime), func(c net.Conn) {
|
||||
go s.handleChan(c)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//pmux tunnel
|
||||
func (s *TRPClient) newChan() {
|
||||
tunnel, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl)
|
||||
if err != nil {
|
||||
logs.Error("connect to ", s.svrAddr, "error:", err)
|
||||
return
|
||||
}
|
||||
s.tunnel = nps_mux.NewMux(tunnel.Conn, s.bridgeConnType, s.disconnectTime)
|
||||
for {
|
||||
src, err := s.tunnel.Accept()
|
||||
if err != nil {
|
||||
logs.Warn(err)
|
||||
s.Close()
|
||||
break
|
||||
}
|
||||
go s.handleChan(src)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TRPClient) handleChan(src net.Conn) {
|
||||
lk, err := conn.NewConn(src).GetLinkInfo()
|
||||
if err != nil || lk == nil {
|
||||
src.Close()
|
||||
logs.Error("get connection info from server error ", err)
|
||||
return
|
||||
}
|
||||
//host for target processing
|
||||
lk.Host = common.FormatAddress(lk.Host)
|
||||
//if Conn type is http, read the request and log
|
||||
if lk.ConnType == "http" {
|
||||
if targetConn, err := net.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil {
|
||||
logs.Warn("connect to %s error %s", lk.Host, err.Error())
|
||||
src.Close()
|
||||
} else {
|
||||
srcConn := conn.GetConn(src, lk.Crypt, lk.Compress, nil, false)
|
||||
go func() {
|
||||
common.CopyBuffer(srcConn, targetConn)
|
||||
srcConn.Close()
|
||||
targetConn.Close()
|
||||
}()
|
||||
for {
|
||||
if r, err := http.ReadRequest(bufio.NewReader(srcConn)); err != nil {
|
||||
srcConn.Close()
|
||||
targetConn.Close()
|
||||
break
|
||||
} else {
|
||||
logs.Trace("http request, method %s, host %s, url %s, remote address %s", r.Method, r.Host, r.URL.Path, r.RemoteAddr)
|
||||
r.Write(targetConn)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if lk.ConnType == "udp5" {
|
||||
logs.Trace("new %s connection with the goal of %s, remote address:%s", lk.ConnType, lk.Host, lk.RemoteAddr)
|
||||
s.handleUdp(src)
|
||||
}
|
||||
//connect to target if conn type is tcp or udp
|
||||
if targetConn, err := net.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil {
|
||||
logs.Warn("connect to %s error %s", lk.Host, err.Error())
|
||||
src.Close()
|
||||
} else {
|
||||
logs.Trace("new %s connection with the goal of %s, remote address:%s", lk.ConnType, lk.Host, lk.RemoteAddr)
|
||||
conn.CopyWaitGroup(src, targetConn, lk.Crypt, lk.Compress, nil, nil, false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TRPClient) handleUdp(serverConn net.Conn) {
|
||||
// bind a local udp port
|
||||
local, err := net.ListenUDP("udp", nil)
|
||||
defer serverConn.Close()
|
||||
if err != nil {
|
||||
logs.Error("bind local udp port error ", err.Error())
|
||||
return
|
||||
}
|
||||
defer local.Close()
|
||||
go func() {
|
||||
defer serverConn.Close()
|
||||
b := common.BufPoolUdp.Get().([]byte)
|
||||
defer common.BufPoolUdp.Put(b)
|
||||
for {
|
||||
n, raddr, err := local.ReadFrom(b)
|
||||
if err != nil {
|
||||
logs.Error("read data from remote server error", err.Error())
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
dgram := common.NewUDPDatagram(common.NewUDPHeader(0, 0, common.ToSocksAddr(raddr)), b[:n])
|
||||
dgram.Write(&buf)
|
||||
b, err := conn.GetLenBytes(buf.Bytes())
|
||||
if err != nil {
|
||||
logs.Warn("get len bytes error", err.Error())
|
||||
continue
|
||||
}
|
||||
if _, err := serverConn.Write(b); err != nil {
|
||||
logs.Error("write data to remote error", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
b := common.BufPoolUdp.Get().([]byte)
|
||||
defer common.BufPoolUdp.Put(b)
|
||||
for {
|
||||
n, err := serverConn.Read(b)
|
||||
if err != nil {
|
||||
logs.Error("read udp data from server error ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
udpData, err := common.ReadUDPDatagram(bytes.NewReader(b[:n]))
|
||||
if err != nil {
|
||||
logs.Error("unpack data error", err.Error())
|
||||
return
|
||||
}
|
||||
raddr, err := net.ResolveUDPAddr("udp", udpData.Header.Addr.String())
|
||||
if err != nil {
|
||||
logs.Error("build remote addr err", err.Error())
|
||||
continue // drop silently
|
||||
}
|
||||
_, err = local.WriteTo(udpData.Data, raddr)
|
||||
if err != nil {
|
||||
logs.Error("write data to remote ", raddr.String(), "error", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the monitor channel is closed
|
||||
func (s *TRPClient) ping() {
|
||||
s.ticker = time.NewTicker(time.Second * 5)
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-s.ticker.C:
|
||||
if s.tunnel != nil && s.tunnel.IsClose {
|
||||
s.Close()
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TRPClient) Close() {
|
||||
s.once.Do(s.closing)
|
||||
}
|
||||
|
||||
func (s *TRPClient) closing() {
|
||||
CloseClient = true
|
||||
NowStatus = 0
|
||||
if s.tunnel != nil {
|
||||
_ = s.tunnel.Close()
|
||||
}
|
||||
if s.signal != nil {
|
||||
_ = s.signal.Close()
|
||||
}
|
||||
if s.ticker != nil {
|
||||
s.ticker.Stop()
|
||||
}
|
||||
}
|
@ -1,526 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/config"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/version"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/xtaci/kcp-go"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
func GetTaskStatus(path string) {
|
||||
cnf, err := config.NewConfig(path)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
c, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if _, err := c.Write([]byte(common.WORK_STATUS)); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
//read now vKey and write to server
|
||||
if f, err := common.ReadAllFromFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt")); err != nil {
|
||||
log.Fatalln(err)
|
||||
} else if _, err := c.Write([]byte(crypt.Md5(string(f)))); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
var isPub bool
|
||||
binary.Read(c, binary.LittleEndian, &isPub)
|
||||
if l, err := c.GetLen(); err != nil {
|
||||
log.Fatalln(err)
|
||||
} else if b, err := c.GetShortContent(l); err != nil {
|
||||
log.Fatalln(err)
|
||||
} else {
|
||||
arr := strings.Split(string(b), common.CONN_DATA_SEQ)
|
||||
for _, v := range cnf.Hosts {
|
||||
if common.InStrArr(arr, v.Remark) {
|
||||
log.Println(v.Remark, "ok")
|
||||
} else {
|
||||
log.Println(v.Remark, "not running")
|
||||
}
|
||||
}
|
||||
for _, v := range cnf.Tasks {
|
||||
ports := common.GetPorts(v.Ports)
|
||||
if v.Mode == "secret" {
|
||||
ports = append(ports, 0)
|
||||
}
|
||||
for _, vv := range ports {
|
||||
var remark string
|
||||
if len(ports) > 1 {
|
||||
remark = v.Remark + "_" + strconv.Itoa(vv)
|
||||
} else {
|
||||
remark = v.Remark
|
||||
}
|
||||
if common.InStrArr(arr, remark) {
|
||||
log.Println(remark, "ok")
|
||||
} else {
|
||||
log.Println(remark, "not running")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var errAdd = errors.New("The server returned an error, which port or host may have been occupied or not allowed to open.")
|
||||
|
||||
func StartFromFile(path string) {
|
||||
first := true
|
||||
cnf, err := config.NewConfig(path)
|
||||
if err != nil || cnf.CommonConfig == nil {
|
||||
logs.Error("Config file %s loading error %s", path, err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
logs.Info("Loading configuration file %s successfully", path)
|
||||
|
||||
re:
|
||||
if first || cnf.CommonConfig.AutoReconnection {
|
||||
if !first {
|
||||
logs.Info("Reconnecting...")
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
first = false
|
||||
c, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
goto re
|
||||
}
|
||||
var isPub bool
|
||||
binary.Read(c, binary.LittleEndian, &isPub)
|
||||
|
||||
// get tmp password
|
||||
var b []byte
|
||||
vkey := cnf.CommonConfig.VKey
|
||||
if isPub {
|
||||
// send global configuration to server and get status of config setting
|
||||
if _, err := c.SendInfo(cnf.CommonConfig.Client, common.NEW_CONF); err != nil {
|
||||
logs.Error(err)
|
||||
goto re
|
||||
}
|
||||
if !c.GetAddStatus() {
|
||||
logs.Error("the web_user may have been occupied!")
|
||||
goto re
|
||||
}
|
||||
|
||||
if b, err = c.GetShortContent(16); err != nil {
|
||||
logs.Error(err)
|
||||
goto re
|
||||
}
|
||||
vkey = string(b)
|
||||
}
|
||||
ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(vkey), 0600)
|
||||
|
||||
//send hosts to server
|
||||
for _, v := range cnf.Hosts {
|
||||
if _, err := c.SendInfo(v, common.NEW_HOST); err != nil {
|
||||
logs.Error(err)
|
||||
goto re
|
||||
}
|
||||
if !c.GetAddStatus() {
|
||||
logs.Error(errAdd, v.Host)
|
||||
goto re
|
||||
}
|
||||
}
|
||||
|
||||
//send task to server
|
||||
for _, v := range cnf.Tasks {
|
||||
if _, err := c.SendInfo(v, common.NEW_TASK); err != nil {
|
||||
logs.Error(err)
|
||||
goto re
|
||||
}
|
||||
if !c.GetAddStatus() {
|
||||
logs.Error(errAdd, v.Ports, v.Remark)
|
||||
goto re
|
||||
}
|
||||
if v.Mode == "file" {
|
||||
//start local file server
|
||||
go startLocalFileServer(cnf.CommonConfig, v, vkey)
|
||||
}
|
||||
}
|
||||
|
||||
//create local server secret or p2p
|
||||
for _, v := range cnf.LocalServer {
|
||||
go StartLocalServer(v, cnf.CommonConfig)
|
||||
}
|
||||
|
||||
c.Close()
|
||||
if cnf.CommonConfig.Client.WebUserName == "" || cnf.CommonConfig.Client.WebPassword == "" {
|
||||
logs.Notice("web access login username:user password:%s", vkey)
|
||||
} else {
|
||||
logs.Notice("web access login username:%s password:%s", cnf.CommonConfig.Client.WebUserName, cnf.CommonConfig.Client.WebPassword)
|
||||
}
|
||||
NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl, cnf, cnf.CommonConfig.DisconnectTime).Start()
|
||||
CloseLocalServer()
|
||||
goto re
|
||||
}
|
||||
|
||||
// Create a new connection with the server and verify it
|
||||
func NewConn(tp string, vkey string, server string, connType string, proxyUrl string) (*conn.Conn, error) {
|
||||
var err error
|
||||
var connection net.Conn
|
||||
var sess *kcp.UDPSession
|
||||
if tp == "tcp" {
|
||||
if proxyUrl != "" {
|
||||
u, er := url.Parse(proxyUrl)
|
||||
if er != nil {
|
||||
return nil, er
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "socks5":
|
||||
n, er := proxy.FromURL(u, nil)
|
||||
if er != nil {
|
||||
return nil, er
|
||||
}
|
||||
connection, err = n.Dial("tcp", server)
|
||||
default:
|
||||
connection, err = NewHttpProxyConn(u, server)
|
||||
}
|
||||
} else {
|
||||
connection, err = net.Dial("tcp", server)
|
||||
}
|
||||
} else {
|
||||
sess, err = kcp.DialWithOptions(server, nil, 10, 3)
|
||||
if err == nil {
|
||||
conn.SetUdpSession(sess)
|
||||
connection = sess
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connection.SetDeadline(time.Now().Add(time.Second * 10))
|
||||
defer connection.SetDeadline(time.Time{})
|
||||
c := conn.NewConn(connection)
|
||||
if _, err := c.Write([]byte(common.CONN_TEST)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.WriteLenContent([]byte(version.GetVersion())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.WriteLenContent([]byte(version.VERSION)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := c.GetShortContent(32)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if crypt.Md5(version.GetVersion()) != string(b) {
|
||||
logs.Error("The client does not match the server version. The current core version of the client is", version.GetVersion())
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s, err := c.ReadFlag(); err != nil {
|
||||
return nil, err
|
||||
} else if s == common.VERIFY_EER {
|
||||
return nil, errors.New(fmt.Sprintf("Validation key %s incorrect", vkey))
|
||||
}
|
||||
if _, err := c.Write([]byte(connType)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.SetAlive(tp)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
//http proxy connection
|
||||
func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {
|
||||
req, err := http.NewRequest("CONNECT", "http://"+remoteAddr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
password, _ := url.User.Password()
|
||||
req.Header.Set("Authorization", "Basic "+basicAuth(strings.Trim(url.User.Username(), " "), password))
|
||||
// we make a http proxy request
|
||||
proxyConn, err := net.Dial("tcp", url.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Write(proxyConn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := http.ReadResponse(bufio.NewReader(proxyConn), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return nil, errors.New("Proxy error " + res.Status)
|
||||
}
|
||||
return proxyConn, nil
|
||||
}
|
||||
|
||||
//get a basic auth string
|
||||
func basicAuth(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
func getRemoteAddressFromServer(rAddr string, localConn *net.UDPConn, md5Password, role string, add int) error {
|
||||
rAddr, err := getNextAddr(rAddr, add)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return err
|
||||
}
|
||||
addr, err := net.ResolveUDPAddr("udp", rAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := localConn.WriteTo(common.GetWriteStr(md5Password, role), addr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleP2PUdp(localAddr, rAddr, md5Password, role string) (remoteAddress string, c net.PacketConn, err error) {
|
||||
localConn, err := newUdpConnByAddr(localAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 0)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
err = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 1)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
err = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 2)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
var remoteAddr1, remoteAddr2, remoteAddr3 string
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
if n, addr, er := localConn.ReadFromUDP(buf); er != nil {
|
||||
err = er
|
||||
return
|
||||
} else {
|
||||
rAddr2, _ := getNextAddr(rAddr, 1)
|
||||
rAddr3, _ := getNextAddr(rAddr, 2)
|
||||
switch addr.String() {
|
||||
case rAddr:
|
||||
remoteAddr1 = string(buf[:n])
|
||||
case rAddr2:
|
||||
remoteAddr2 = string(buf[:n])
|
||||
case rAddr3:
|
||||
remoteAddr3 = string(buf[:n])
|
||||
}
|
||||
}
|
||||
if remoteAddr1 != "" && remoteAddr2 != "" && remoteAddr3 != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if remoteAddress, err = sendP2PTestMsg(localConn, remoteAddr1, remoteAddr2, remoteAddr3); err != nil {
|
||||
return
|
||||
}
|
||||
c, err = newUdpConnByAddr(localAddr)
|
||||
return
|
||||
}
|
||||
|
||||
func sendP2PTestMsg(localConn *net.UDPConn, remoteAddr1, remoteAddr2, remoteAddr3 string) (string, error) {
|
||||
logs.Trace(remoteAddr3, remoteAddr2, remoteAddr1)
|
||||
defer localConn.Close()
|
||||
isClose := false
|
||||
defer func() { isClose = true }()
|
||||
interval, err := getAddrInterval(remoteAddr1, remoteAddr2, remoteAddr3)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
go func() {
|
||||
addr, err := getNextAddr(remoteAddr3, interval)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
remoteUdpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logs.Trace("try send test packet to target %s", addr)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if isClose {
|
||||
return
|
||||
}
|
||||
if _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if interval != 0 {
|
||||
ip := common.GetIpByAddr(remoteAddr2)
|
||||
go func() {
|
||||
ports := getRandomPortArr(common.GetPortByAddr(remoteAddr3), common.GetPortByAddr(remoteAddr3)+interval*50)
|
||||
for i := 0; i <= 50; i++ {
|
||||
go func(port int) {
|
||||
trueAddress := ip + ":" + strconv.Itoa(port)
|
||||
logs.Trace("try send test packet to target %s", trueAddress)
|
||||
remoteUdpAddr, err := net.ResolveUDPAddr("udp", trueAddress)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(time.Second * 2)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if isClose {
|
||||
return
|
||||
}
|
||||
if _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ports[i])
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
for {
|
||||
localConn.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||
n, addr, err := localConn.ReadFromUDP(buf)
|
||||
localConn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch string(buf[:n]) {
|
||||
case common.WORK_P2P_SUCCESS:
|
||||
for i := 20; i > 0; i-- {
|
||||
if _, err = localConn.WriteTo([]byte(common.WORK_P2P_END), addr); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return addr.String(), nil
|
||||
case common.WORK_P2P_END:
|
||||
logs.Trace("Remotely Address %s Reply Packet Successfully Received", addr.String())
|
||||
return addr.String(), nil
|
||||
case common.WORK_P2P_CONNECT:
|
||||
go func() {
|
||||
for i := 20; i > 0; i-- {
|
||||
logs.Trace("try send receive success packet to target %s", addr.String())
|
||||
if _, err = localConn.WriteTo([]byte(common.WORK_P2P_SUCCESS), addr); err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return "", errors.New("connect to the target failed, maybe the nat type is not support p2p")
|
||||
}
|
||||
|
||||
func newUdpConnByAddr(addr string) (*net.UDPConn, error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return udpConn, nil
|
||||
}
|
||||
|
||||
func getNextAddr(addr string, n int) (string, error) {
|
||||
arr := strings.Split(addr, ":")
|
||||
if len(arr) != 2 {
|
||||
return "", errors.New(fmt.Sprintf("the format of %s incorrect", addr))
|
||||
}
|
||||
if p, err := strconv.Atoi(arr[1]); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return arr[0] + ":" + strconv.Itoa(p+n), nil
|
||||
}
|
||||
}
|
||||
|
||||
func getAddrInterval(addr1, addr2, addr3 string) (int, error) {
|
||||
arr1 := strings.Split(addr1, ":")
|
||||
if len(arr1) != 2 {
|
||||
return 0, errors.New(fmt.Sprintf("the format of %s incorrect", addr1))
|
||||
}
|
||||
arr2 := strings.Split(addr2, ":")
|
||||
if len(arr2) != 2 {
|
||||
return 0, errors.New(fmt.Sprintf("the format of %s incorrect", addr2))
|
||||
}
|
||||
arr3 := strings.Split(addr3, ":")
|
||||
if len(arr3) != 2 {
|
||||
return 0, errors.New(fmt.Sprintf("the format of %s incorrect", addr3))
|
||||
}
|
||||
p1, err := strconv.Atoi(arr1[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
p2, err := strconv.Atoi(arr2[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
p3, err := strconv.Atoi(arr3[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
interVal := int(math.Floor(math.Min(math.Abs(float64(p3-p2)), math.Abs(float64(p2-p1)))))
|
||||
if p3-p1 < 0 {
|
||||
return -interVal, nil
|
||||
}
|
||||
return interVal, nil
|
||||
}
|
||||
|
||||
func getRandomPortArr(min, max int) []int {
|
||||
if min > max {
|
||||
min, max = max, min
|
||||
}
|
||||
addrAddr := make([]int, max-min+1)
|
||||
for i := min; i <= max; i++ {
|
||||
addrAddr[max-i] = i
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
var r, temp int
|
||||
for i := max - min; i > 0; i-- {
|
||||
r = rand.Int() % i
|
||||
temp = addrAddr[i]
|
||||
addrAddr[i] = addrAddr[r]
|
||||
addrAddr[r] = temp
|
||||
}
|
||||
return addrAddr
|
||||
}
|
102
client/health.go
@ -1,102 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/lib/sheap"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var isStart bool
|
||||
var serverConn *conn.Conn
|
||||
|
||||
func heathCheck(healths []*file.Health, c *conn.Conn) bool {
|
||||
serverConn = c
|
||||
if isStart {
|
||||
for _, v := range healths {
|
||||
v.HealthMap = make(map[string]int)
|
||||
}
|
||||
return true
|
||||
}
|
||||
isStart = true
|
||||
h := &sheap.IntHeap{}
|
||||
for _, v := range healths {
|
||||
if v.HealthMaxFail > 0 && v.HealthCheckTimeout > 0 && v.HealthCheckInterval > 0 {
|
||||
v.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second)
|
||||
heap.Push(h, v.HealthNextTime.Unix())
|
||||
v.HealthMap = make(map[string]int)
|
||||
}
|
||||
}
|
||||
go session(healths, h)
|
||||
return true
|
||||
}
|
||||
|
||||
func session(healths []*file.Health, h *sheap.IntHeap) {
|
||||
for {
|
||||
if h.Len() == 0 {
|
||||
logs.Error("health check error")
|
||||
break
|
||||
}
|
||||
rs := heap.Pop(h).(int64) - time.Now().Unix()
|
||||
if rs <= 0 {
|
||||
continue
|
||||
}
|
||||
timer := time.NewTimer(time.Duration(rs) * time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
for _, v := range healths {
|
||||
if v.HealthNextTime.Before(time.Now()) {
|
||||
v.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second)
|
||||
//check
|
||||
go check(v)
|
||||
//reset time
|
||||
heap.Push(h, v.HealthNextTime.Unix())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// work when just one port and many target
|
||||
func check(t *file.Health) {
|
||||
arr := strings.Split(t.HealthCheckTarget, ",")
|
||||
var err error
|
||||
var rs *http.Response
|
||||
for _, v := range arr {
|
||||
if t.HealthCheckType == "tcp" {
|
||||
var c net.Conn
|
||||
c, err = net.DialTimeout("tcp", v, time.Duration(t.HealthCheckTimeout)*time.Second)
|
||||
if err == nil {
|
||||
c.Close()
|
||||
}
|
||||
} else {
|
||||
client := &http.Client{}
|
||||
client.Timeout = time.Duration(t.HealthCheckTimeout) * time.Second
|
||||
rs, err = client.Get("http://" + v + t.HttpHealthUrl)
|
||||
if err == nil && rs.StatusCode != 200 {
|
||||
err = errors.New("status code is not match")
|
||||
}
|
||||
}
|
||||
t.Lock()
|
||||
if err != nil {
|
||||
t.HealthMap[v] += 1
|
||||
} else if t.HealthMap[v] >= t.HealthMaxFail {
|
||||
//send recovery add
|
||||
serverConn.SendHealthInfo(v, "1")
|
||||
t.HealthMap[v] = 0
|
||||
}
|
||||
|
||||
if t.HealthMap[v] > 0 && t.HealthMap[v]%t.HealthMaxFail == 0 {
|
||||
//send fail remove
|
||||
serverConn.SendHealthInfo(v, "0")
|
||||
}
|
||||
t.Unlock()
|
||||
}
|
||||
}
|
219
client/local.go
@ -1,219 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"ehang.io/nps-mux"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/config"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/server/proxy"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
var (
|
||||
LocalServer []*net.TCPListener
|
||||
udpConn net.Conn
|
||||
muxSession *nps_mux.Mux
|
||||
fileServer []*http.Server
|
||||
p2pNetBridge *p2pBridge
|
||||
lock sync.RWMutex
|
||||
udpConnStatus bool
|
||||
)
|
||||
|
||||
type p2pBridge struct {
|
||||
}
|
||||
|
||||
func (p2pBridge *p2pBridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) {
|
||||
for i := 0; muxSession == nil; i++ {
|
||||
if i >= 20 {
|
||||
err = errors.New("p2pBridge:too many times to get muxSession")
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
runtime.Gosched() // waiting for another goroutine establish the mux connection
|
||||
}
|
||||
nowConn, err := muxSession.NewConn()
|
||||
if err != nil {
|
||||
udpConn = nil
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.NewConn(nowConn).SendInfo(link, ""); err != nil {
|
||||
udpConnStatus = false
|
||||
return nil, err
|
||||
}
|
||||
return nowConn, nil
|
||||
}
|
||||
|
||||
func CloseLocalServer() {
|
||||
for _, v := range LocalServer {
|
||||
v.Close()
|
||||
}
|
||||
for _, v := range fileServer {
|
||||
v.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey string) {
|
||||
remoteConn, err := NewConn(config.Tp, vkey, config.Server, common.WORK_FILE, config.ProxyUrl)
|
||||
if err != nil {
|
||||
logs.Error("Local connection server failed ", err.Error())
|
||||
return
|
||||
}
|
||||
srv := &http.Server{
|
||||
Handler: http.StripPrefix(t.StripPre, http.FileServer(http.Dir(t.LocalPath))),
|
||||
}
|
||||
logs.Info("start local file system, local path %s, strip prefix %s ,remote port %s ", t.LocalPath, t.StripPre, t.Ports)
|
||||
fileServer = append(fileServer, srv)
|
||||
listener := nps_mux.NewMux(remoteConn.Conn, common.CONN_TCP, config.DisconnectTime)
|
||||
logs.Error(srv.Serve(listener))
|
||||
}
|
||||
|
||||
func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error {
|
||||
if l.Type != "secret" {
|
||||
go handleUdpMonitor(config, l)
|
||||
}
|
||||
task := &file.Tunnel{
|
||||
Port: l.Port,
|
||||
ServerIp: "0.0.0.0",
|
||||
Status: true,
|
||||
Client: &file.Client{
|
||||
Cnf: &file.Config{
|
||||
U: "",
|
||||
P: "",
|
||||
Compress: config.Client.Cnf.Compress,
|
||||
},
|
||||
Status: true,
|
||||
RateLimit: 0,
|
||||
Flow: &file.Flow{},
|
||||
},
|
||||
Flow: &file.Flow{},
|
||||
Target: &file.Target{},
|
||||
}
|
||||
switch l.Type {
|
||||
case "p2ps":
|
||||
logs.Info("successful start-up of local socks5 monitoring, port", l.Port)
|
||||
return proxy.NewSock5ModeServer(p2pNetBridge, task).Start()
|
||||
case "p2pt":
|
||||
logs.Info("successful start-up of local tcp trans monitoring, port", l.Port)
|
||||
return proxy.NewTunnelModeServer(proxy.HandleTrans, p2pNetBridge, task).Start()
|
||||
case "p2p", "secret":
|
||||
listener, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), l.Port, ""})
|
||||
if err != nil {
|
||||
logs.Error("local listener startup failed port %d, error %s", l.Port, err.Error())
|
||||
return err
|
||||
}
|
||||
LocalServer = append(LocalServer, listener)
|
||||
logs.Info("successful start-up of local tcp monitoring, port", l.Port)
|
||||
conn.Accept(listener, func(c net.Conn) {
|
||||
logs.Trace("new %s connection", l.Type)
|
||||
if l.Type == "secret" {
|
||||
handleSecret(c, config, l)
|
||||
} else if l.Type == "p2p" {
|
||||
handleP2PVisitor(c, config, l)
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUdpMonitor(config *config.CommonConfig, l *config.LocalServer) {
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if !udpConnStatus {
|
||||
udpConn = nil
|
||||
tmpConn, err := common.GetLocalUdpAddr()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
logs.Notice("try to connect to the server", i+1)
|
||||
newUdpConn(tmpConn.LocalAddr().String(), config, l)
|
||||
if udpConn != nil {
|
||||
udpConnStatus = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) {
|
||||
remoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_SECRET, config.ProxyUrl)
|
||||
if err != nil {
|
||||
logs.Error("Local connection server failed ", err.Error())
|
||||
return
|
||||
}
|
||||
if _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil {
|
||||
logs.Error("Local connection server failed ", err.Error())
|
||||
return
|
||||
}
|
||||
conn.CopyWaitGroup(remoteConn.Conn, localTcpConn, false, false, nil, nil, false, nil)
|
||||
}
|
||||
|
||||
func handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) {
|
||||
if udpConn == nil {
|
||||
logs.Notice("new conn, P2P can not penetrate successfully, traffic will be transferred through the server")
|
||||
handleSecret(localTcpConn, config, l)
|
||||
return
|
||||
}
|
||||
logs.Trace("start trying to connect with the server")
|
||||
//TODO just support compress now because there is not tls file in client packages
|
||||
link := conn.NewLink(common.CONN_TCP, l.Target, false, config.Client.Cnf.Compress, localTcpConn.LocalAddr().String(), false)
|
||||
if target, err := p2pNetBridge.SendLinkInfo(0, link, nil); err != nil {
|
||||
logs.Error(err)
|
||||
udpConnStatus = false
|
||||
return
|
||||
} else {
|
||||
conn.CopyWaitGroup(target, localTcpConn, false, config.Client.Cnf.Compress, nil, nil, false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func newUdpConn(localAddr string, config *config.CommonConfig, l *config.LocalServer) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
remoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_P2P, config.ProxyUrl)
|
||||
if err != nil {
|
||||
logs.Error("Local connection server failed ", err.Error())
|
||||
return
|
||||
}
|
||||
if _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil {
|
||||
logs.Error("Local connection server failed ", err.Error())
|
||||
return
|
||||
}
|
||||
var rAddr []byte
|
||||
//读取服务端地址、密钥 继续做处理
|
||||
if rAddr, err = remoteConn.GetShortLenContent(); err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
var localConn net.PacketConn
|
||||
var remoteAddress string
|
||||
if remoteAddress, localConn, err = handleP2PUdp(localAddr, string(rAddr), crypt.Md5(l.Password), common.WORK_P2P_VISITOR); err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
udpTunnel, err := kcp.NewConn(remoteAddress, nil, 150, 3, localConn)
|
||||
if err != nil || udpTunnel == nil {
|
||||
logs.Warn(err)
|
||||
return
|
||||
}
|
||||
logs.Trace("successful create a connection with server", remoteAddress)
|
||||
conn.SetUdpSession(udpTunnel)
|
||||
udpConn = udpTunnel
|
||||
muxSession = nps_mux.NewMux(udpConn, "kcp", config.DisconnectTime)
|
||||
p2pNetBridge = &p2pBridge{}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
)
|
||||
|
||||
func RegisterLocalIp(server string, vKey string, tp string, proxyUrl string, hour int) {
|
||||
c, err := NewConn(tp, vKey, server, common.WORK_REGISTER, proxyUrl)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if err := binary.Write(c, binary.LittleEndian, int32(hour)); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("Successful ip registration for local public network, the validity period is %d hours.", hour)
|
||||
os.Exit(0)
|
||||
}
|
246
cmd/npc/npc.go
@ -1,246 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"ehang.io/nps/client"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/config"
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/lib/install"
|
||||
"ehang.io/nps/lib/version"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/ccding/go-stun/stun"
|
||||
"github.com/kardianos/service"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
serverAddr = flag.String("server", "", "Server addr (ip:port)")
|
||||
configPath = flag.String("config", "", "Configuration file path")
|
||||
verifyKey = flag.String("vkey", "", "Authentication key")
|
||||
logType = flag.String("log", "stdout", "Log output mode(stdout|file)")
|
||||
connType = flag.String("type", "tcp", "Connection type with the server(kcp|tcp)")
|
||||
proxyUrl = flag.String("proxy", "", "proxy socks5 url(eg:socks5://111:222@127.0.0.1:9007)")
|
||||
logLevel = flag.String("log_level", "7", "log level 0~7")
|
||||
registerTime = flag.Int("time", 2, "register time long /h")
|
||||
localPort = flag.Int("local_port", 2000, "p2p local port")
|
||||
password = flag.String("password", "", "p2p password flag")
|
||||
target = flag.String("target", "", "p2p target")
|
||||
localType = flag.String("local_type", "p2p", "p2p target")
|
||||
logPath = flag.String("log_path", "", "npc log path")
|
||||
debug = flag.Bool("debug", true, "npc debug")
|
||||
pprofAddr = flag.String("pprof", "", "PProf debug addr (ip:port)")
|
||||
stunAddr = flag.String("stun_addr", "stun.stunprotocol.org:3478", "stun server address (eg:stun.stunprotocol.org:3478)")
|
||||
ver = flag.Bool("version", false, "show current version")
|
||||
disconnectTime = flag.Int("disconnect_timeout", 60, "not receiving check packet times, until timeout will disconnect the client")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
logs.Reset()
|
||||
logs.EnableFuncCallDepth(true)
|
||||
logs.SetLogFuncCallDepth(3)
|
||||
if *ver {
|
||||
common.PrintVersion()
|
||||
return
|
||||
}
|
||||
if *logPath == "" {
|
||||
*logPath = common.GetNpcLogPath()
|
||||
}
|
||||
if common.IsWindows() {
|
||||
*logPath = strings.Replace(*logPath, "\\", "\\\\", -1)
|
||||
}
|
||||
if *debug {
|
||||
logs.SetLogger(logs.AdapterConsole, `{"level":`+*logLevel+`,"color":true}`)
|
||||
} else {
|
||||
logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"`+*logPath+`","daily":false,"maxlines":100000,"color":true}`)
|
||||
}
|
||||
|
||||
// init service
|
||||
options := make(service.KeyValue)
|
||||
svcConfig := &service.Config{
|
||||
Name: "Npc",
|
||||
DisplayName: "nps内网穿透客户端",
|
||||
Description: "一款轻量级、功能强大的内网穿透代理服务器。支持tcp、udp流量转发,支持内网http代理、内网socks5代理,同时支持snappy压缩、站点保护、加密传输、多路复用、header修改等。支持web图形化管理,集成多用户模式。",
|
||||
Option: options,
|
||||
}
|
||||
if !common.IsWindows() {
|
||||
svcConfig.Dependencies = []string{
|
||||
"Requires=network.target",
|
||||
"After=network-online.target syslog.target"}
|
||||
svcConfig.Option["SystemdScript"] = install.SystemdScript
|
||||
svcConfig.Option["SysvScript"] = install.SysvScript
|
||||
}
|
||||
for _, v := range os.Args[1:] {
|
||||
switch v {
|
||||
case "install", "start", "stop", "uninstall", "restart":
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(v, "-service=") && !strings.Contains(v, "-debug=") {
|
||||
svcConfig.Arguments = append(svcConfig.Arguments, v)
|
||||
}
|
||||
}
|
||||
svcConfig.Arguments = append(svcConfig.Arguments, "-debug=false")
|
||||
prg := &npc{
|
||||
exit: make(chan struct{}),
|
||||
}
|
||||
s, err := service.New(prg, svcConfig)
|
||||
if err != nil {
|
||||
logs.Error(err, "service function disabled")
|
||||
run()
|
||||
// run without service
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
switch os.Args[1] {
|
||||
case "status":
|
||||
if len(os.Args) > 2 {
|
||||
path := strings.Replace(os.Args[2], "-config=", "", -1)
|
||||
client.GetTaskStatus(path)
|
||||
}
|
||||
case "register":
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
client.RegisterLocalIp(*serverAddr, *verifyKey, *connType, *proxyUrl, *registerTime)
|
||||
case "update":
|
||||
install.UpdateNpc()
|
||||
return
|
||||
case "nat":
|
||||
c := stun.NewClient()
|
||||
c.SetServerAddr(*stunAddr)
|
||||
nat, host, err := c.Discover()
|
||||
if err != nil || host == nil {
|
||||
logs.Error("get nat type error", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("nat type: %s \npublic address: %s\n", nat.String(), host.String())
|
||||
os.Exit(0)
|
||||
case "start", "stop", "restart":
|
||||
// support busyBox and sysV, for openWrt
|
||||
if service.Platform() == "unix-systemv" {
|
||||
logs.Info("unix-systemv service")
|
||||
cmd := exec.Command("/etc/init.d/"+svcConfig.Name, os.Args[1])
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err := service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
|
||||
}
|
||||
return
|
||||
case "install":
|
||||
service.Control(s, "stop")
|
||||
service.Control(s, "uninstall")
|
||||
install.InstallNpc()
|
||||
err := service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
|
||||
}
|
||||
if service.Platform() == "unix-systemv" {
|
||||
logs.Info("unix-systemv service")
|
||||
confPath := "/etc/init.d/" + svcConfig.Name
|
||||
os.Symlink(confPath, "/etc/rc.d/S90"+svcConfig.Name)
|
||||
os.Symlink(confPath, "/etc/rc.d/K02"+svcConfig.Name)
|
||||
}
|
||||
return
|
||||
case "uninstall":
|
||||
err := service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
|
||||
}
|
||||
if service.Platform() == "unix-systemv" {
|
||||
logs.Info("unix-systemv service")
|
||||
os.Remove("/etc/rc.d/S90" + svcConfig.Name)
|
||||
os.Remove("/etc/rc.d/K02" + svcConfig.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
s.Run()
|
||||
}
|
||||
|
||||
type npc struct {
|
||||
exit chan struct{}
|
||||
}
|
||||
|
||||
func (p *npc) Start(s service.Service) error {
|
||||
go p.run()
|
||||
return nil
|
||||
}
|
||||
func (p *npc) Stop(s service.Service) error {
|
||||
close(p.exit)
|
||||
if service.Interactive() {
|
||||
os.Exit(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *npc) run() error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
logs.Warning("npc: panic serving %v: %v\n%s", err, string(buf))
|
||||
}
|
||||
}()
|
||||
run()
|
||||
select {
|
||||
case <-p.exit:
|
||||
logs.Warning("stop...")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func run() {
|
||||
common.InitPProfFromArg(*pprofAddr)
|
||||
//p2p or secret command
|
||||
if *password != "" {
|
||||
commonConfig := new(config.CommonConfig)
|
||||
commonConfig.Server = *serverAddr
|
||||
commonConfig.VKey = *verifyKey
|
||||
commonConfig.Tp = *connType
|
||||
localServer := new(config.LocalServer)
|
||||
localServer.Type = *localType
|
||||
localServer.Password = *password
|
||||
localServer.Target = *target
|
||||
localServer.Port = *localPort
|
||||
commonConfig.Client = new(file.Client)
|
||||
commonConfig.Client.Cnf = new(file.Config)
|
||||
go client.StartLocalServer(localServer, commonConfig)
|
||||
return
|
||||
}
|
||||
env := common.GetEnvMap()
|
||||
if *serverAddr == "" {
|
||||
*serverAddr, _ = env["NPC_SERVER_ADDR"]
|
||||
}
|
||||
if *verifyKey == "" {
|
||||
*verifyKey, _ = env["NPC_SERVER_VKEY"]
|
||||
}
|
||||
logs.Info("the version of client is %s, the core version of client is %s", version.VERSION, version.GetVersion())
|
||||
if *verifyKey != "" && *serverAddr != "" && *configPath == "" {
|
||||
go func() {
|
||||
for {
|
||||
client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl, nil, *disconnectTime).Start()
|
||||
logs.Info("Client closed! It will be reconnected in five seconds")
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
if *configPath == "" {
|
||||
*configPath = common.GetConfigPath()
|
||||
}
|
||||
go client.StartFromFile(*configPath)
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"ehang.io/nps/client"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/version"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
var cl *client.TRPClient
|
||||
|
||||
//export StartClientByVerifyKey
|
||||
func StartClientByVerifyKey(serverAddr, verifyKey, connType, proxyUrl *C.char) int {
|
||||
_ = logs.SetLogger("store")
|
||||
if cl != nil {
|
||||
cl.Close()
|
||||
}
|
||||
cl = client.NewRPClient(C.GoString(serverAddr), C.GoString(verifyKey), C.GoString(connType), C.GoString(proxyUrl), nil, 60)
|
||||
cl.Start()
|
||||
return 1
|
||||
}
|
||||
|
||||
//export GetClientStatus
|
||||
func GetClientStatus() int {
|
||||
return client.NowStatus
|
||||
}
|
||||
|
||||
//export CloseClient
|
||||
func CloseClient() {
|
||||
if cl != nil {
|
||||
cl.Close()
|
||||
}
|
||||
}
|
||||
|
||||
//export Version
|
||||
func Version() *C.char {
|
||||
return C.CString(version.VERSION)
|
||||
}
|
||||
|
||||
//export Logs
|
||||
func Logs() *C.char {
|
||||
return C.CString(common.GetLogMsg())
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Need a main function to make CGO compile package as C shared library
|
||||
}
|
213
cmd/nps/nps.go
@ -1,213 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/lib/install"
|
||||
"ehang.io/nps/lib/version"
|
||||
"ehang.io/nps/server"
|
||||
"ehang.io/nps/server/connection"
|
||||
"ehang.io/nps/server/tool"
|
||||
"ehang.io/nps/web/routers"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/daemon"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
var (
|
||||
level string
|
||||
ver = flag.Bool("version", false, "show current version")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
// init log
|
||||
if *ver {
|
||||
common.PrintVersion()
|
||||
return
|
||||
}
|
||||
if err := beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf")); err != nil {
|
||||
log.Fatalln("load config file error", err.Error())
|
||||
}
|
||||
common.InitPProfFromFile()
|
||||
if level = beego.AppConfig.String("log_level"); level == "" {
|
||||
level = "7"
|
||||
}
|
||||
logs.Reset()
|
||||
logs.EnableFuncCallDepth(true)
|
||||
logs.SetLogFuncCallDepth(3)
|
||||
logPath := beego.AppConfig.String("log_path")
|
||||
if logPath == "" {
|
||||
logPath = common.GetLogPath()
|
||||
}
|
||||
if common.IsWindows() {
|
||||
logPath = strings.Replace(logPath, "\\", "\\\\", -1)
|
||||
}
|
||||
// init service
|
||||
options := make(service.KeyValue)
|
||||
svcConfig := &service.Config{
|
||||
Name: "Nps",
|
||||
DisplayName: "nps内网穿透代理服务器",
|
||||
Description: "一款轻量级、功能强大的内网穿透代理服务器。支持tcp、udp流量转发,支持内网http代理、内网socks5代理,同时支持snappy压缩、站点保护、加密传输、多路复用、header修改等。支持web图形化管理,集成多用户模式。",
|
||||
Option: options,
|
||||
}
|
||||
svcConfig.Arguments = append(svcConfig.Arguments, "service")
|
||||
if len(os.Args) > 1 && os.Args[1] == "service" {
|
||||
_ = logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"`+logPath+`","daily":false,"maxlines":100000,"color":true}`)
|
||||
} else {
|
||||
_ = logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
|
||||
}
|
||||
if !common.IsWindows() {
|
||||
svcConfig.Dependencies = []string{
|
||||
"Requires=network.target",
|
||||
"After=network-online.target syslog.target"}
|
||||
svcConfig.Option["SystemdScript"] = install.SystemdScript
|
||||
svcConfig.Option["SysvScript"] = install.SysvScript
|
||||
}
|
||||
prg := &nps{}
|
||||
prg.exit = make(chan struct{})
|
||||
s, err := service.New(prg, svcConfig)
|
||||
if err != nil {
|
||||
logs.Error(err, "service function disabled")
|
||||
run()
|
||||
// run without service
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
if len(os.Args) > 1 && os.Args[1] != "service" {
|
||||
switch os.Args[1] {
|
||||
case "reload":
|
||||
daemon.InitDaemon("nps", common.GetRunPath(), common.GetTmpPath())
|
||||
return
|
||||
case "install":
|
||||
// uninstall before
|
||||
_ = service.Control(s, "stop")
|
||||
_ = service.Control(s, "uninstall")
|
||||
|
||||
binPath := install.InstallNps()
|
||||
svcConfig.Executable = binPath
|
||||
s, err := service.New(prg, svcConfig)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
err = service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
|
||||
}
|
||||
if service.Platform() == "unix-systemv" {
|
||||
logs.Info("unix-systemv service")
|
||||
confPath := "/etc/init.d/" + svcConfig.Name
|
||||
os.Symlink(confPath, "/etc/rc.d/S90"+svcConfig.Name)
|
||||
os.Symlink(confPath, "/etc/rc.d/K02"+svcConfig.Name)
|
||||
}
|
||||
return
|
||||
case "start", "restart", "stop":
|
||||
if service.Platform() == "unix-systemv" {
|
||||
logs.Info("unix-systemv service")
|
||||
cmd := exec.Command("/etc/init.d/"+svcConfig.Name, os.Args[1])
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err := service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
|
||||
}
|
||||
return
|
||||
case "uninstall":
|
||||
err := service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
|
||||
}
|
||||
if service.Platform() == "unix-systemv" {
|
||||
logs.Info("unix-systemv service")
|
||||
os.Remove("/etc/rc.d/S90" + svcConfig.Name)
|
||||
os.Remove("/etc/rc.d/K02" + svcConfig.Name)
|
||||
}
|
||||
return
|
||||
case "update":
|
||||
install.UpdateNps()
|
||||
return
|
||||
default:
|
||||
logs.Error("command is not support")
|
||||
return
|
||||
}
|
||||
}
|
||||
_ = s.Run()
|
||||
}
|
||||
|
||||
type nps struct {
|
||||
exit chan struct{}
|
||||
}
|
||||
|
||||
func (p *nps) Start(s service.Service) error {
|
||||
_, _ = s.Status()
|
||||
go p.run()
|
||||
return nil
|
||||
}
|
||||
func (p *nps) Stop(s service.Service) error {
|
||||
_, _ = s.Status()
|
||||
close(p.exit)
|
||||
if service.Interactive() {
|
||||
os.Exit(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *nps) run() error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
logs.Warning("nps: panic serving %v: %v\n%s", err, string(buf))
|
||||
}
|
||||
}()
|
||||
run()
|
||||
select {
|
||||
case <-p.exit:
|
||||
logs.Warning("stop...")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func run() {
|
||||
routers.Init()
|
||||
task := &file.Tunnel{
|
||||
Mode: "webServer",
|
||||
}
|
||||
bridgePort, err := beego.AppConfig.Int("bridge_port")
|
||||
if err != nil {
|
||||
logs.Error("Getting bridge_port error", err)
|
||||
os.Exit(0)
|
||||
}
|
||||
logs.Info("the version of server is %s ,allow client core version to be %s", version.VERSION, version.GetVersion())
|
||||
connection.InitConnectionService()
|
||||
//crypt.InitTls(filepath.Join(common.GetRunPath(), "conf", "server.pem"), filepath.Join(common.GetRunPath(), "conf", "server.key"))
|
||||
crypt.InitTls()
|
||||
tool.InitAllowPort()
|
||||
tool.StartSystemInfo()
|
||||
timeout, err := beego.AppConfig.Int("disconnect_timeout")
|
||||
if err != nil {
|
||||
timeout = 60
|
||||
}
|
||||
go server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridge_type"), timeout)
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
# key -> user | value -> pwd
|
||||
npc=npc.pwd
|
@ -1,78 +0,0 @@
|
||||
[common]
|
||||
server_addr=127.0.0.1:8024
|
||||
conn_type=tcp
|
||||
vkey=123
|
||||
auto_reconnection=true
|
||||
max_conn=1000
|
||||
flow_limit=1000
|
||||
rate_limit=1000
|
||||
basic_username=11
|
||||
basic_password=3
|
||||
web_username=user
|
||||
web_password=1234
|
||||
crypt=true
|
||||
compress=true
|
||||
#pprof_addr=0.0.0.0:9999
|
||||
disconnect_timeout=60
|
||||
|
||||
[health_check_test1]
|
||||
health_check_timeout=1
|
||||
health_check_max_failed=3
|
||||
health_check_interval=1
|
||||
health_http_url=/
|
||||
health_check_type=http
|
||||
health_check_target=127.0.0.1:8083,127.0.0.1:8082
|
||||
|
||||
[health_check_test2]
|
||||
health_check_timeout=1
|
||||
health_check_max_failed=3
|
||||
health_check_interval=1
|
||||
health_check_type=tcp
|
||||
health_check_target=127.0.0.1:8083,127.0.0.1:8082
|
||||
|
||||
[web]
|
||||
host=c.o.com
|
||||
target_addr=127.0.0.1:8083,127.0.0.1:8082
|
||||
|
||||
[tcp]
|
||||
mode=tcp
|
||||
target_addr=127.0.0.1:8080
|
||||
server_port=10000
|
||||
|
||||
[socks5]
|
||||
mode=socks5
|
||||
server_port=19009
|
||||
multi_account=multi_account.conf
|
||||
|
||||
[file]
|
||||
mode=file
|
||||
server_port=19008
|
||||
local_path=/Users/liuhe/Downloads
|
||||
strip_pre=/web/
|
||||
|
||||
[http]
|
||||
mode=httpProxy
|
||||
server_port=19004
|
||||
|
||||
[udp]
|
||||
mode=udp
|
||||
server_port=12253
|
||||
target_addr=114.114.114.114:53
|
||||
|
||||
[ssh_secret]
|
||||
mode=secret
|
||||
password=ssh2
|
||||
target_addr=123.206.77.88:22
|
||||
|
||||
[ssh_p2p]
|
||||
mode=p2p
|
||||
password=ssh3
|
||||
|
||||
[secret_ssh]
|
||||
local_port=2001
|
||||
password=ssh2
|
||||
|
||||
[p2p_ssh]
|
||||
local_port=2002
|
||||
password=ssh3
|
||||
target_addr=123.206.77.88:22
|
@ -1,85 +0,0 @@
|
||||
appname = nps
|
||||
#Boot mode(dev|pro)
|
||||
runmode = dev
|
||||
|
||||
#HTTP(S) proxy port, no startup if empty
|
||||
http_proxy_ip=0.0.0.0
|
||||
http_proxy_port=80
|
||||
https_proxy_port=443
|
||||
https_just_proxy=true
|
||||
#default https certificate setting
|
||||
https_default_cert_file=conf/server.pem
|
||||
https_default_key_file=conf/server.key
|
||||
|
||||
##bridge
|
||||
bridge_type=tcp
|
||||
bridge_port=8024
|
||||
bridge_ip=0.0.0.0
|
||||
|
||||
# Public password, which clients can use to connect to the server
|
||||
# After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file.
|
||||
public_vkey=123
|
||||
|
||||
#Traffic data persistence interval(minute)
|
||||
#Ignorance means no persistence
|
||||
#flow_store_interval=1
|
||||
|
||||
# log level LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7
|
||||
log_level=7
|
||||
#log_path=nps.log
|
||||
|
||||
#Whether to restrict IP access, true or false or ignore
|
||||
#ip_limit=true
|
||||
|
||||
#p2p
|
||||
#p2p_ip=127.0.0.1
|
||||
#p2p_port=6000
|
||||
|
||||
#web
|
||||
web_host=a.o.com
|
||||
web_username=admin
|
||||
web_password=123
|
||||
web_port = 8080
|
||||
web_ip=0.0.0.0
|
||||
web_base_url=
|
||||
web_open_ssl=false
|
||||
web_cert_file=conf/server.pem
|
||||
web_key_file=conf/server.key
|
||||
# if web under proxy use sub path. like http://host/nps need this.
|
||||
#web_base_url=/nps
|
||||
|
||||
#Web API unauthenticated IP address(the len of auth_crypt_key must be 16)
|
||||
#Remove comments if needed
|
||||
#auth_key=test
|
||||
auth_crypt_key =1234567812345678
|
||||
|
||||
#allow_ports=9001-9009,10001,11000-12000
|
||||
|
||||
#Web management multi-user login
|
||||
allow_user_login=false
|
||||
allow_user_register=false
|
||||
allow_user_change_username=false
|
||||
|
||||
|
||||
#extension
|
||||
allow_flow_limit=false
|
||||
allow_rate_limit=false
|
||||
allow_tunnel_num_limit=false
|
||||
allow_local_proxy=false
|
||||
allow_connection_num_limit=false
|
||||
allow_multi_ip=false
|
||||
system_info_display=false
|
||||
|
||||
#cache
|
||||
http_cache=false
|
||||
http_cache_length=100
|
||||
|
||||
#get origin ip
|
||||
http_add_origin_header=false
|
||||
|
||||
#pprof debug options
|
||||
#pprof_ip=0.0.0.0
|
||||
#pprof_port=9999
|
||||
|
||||
#client disconnect timeout
|
||||
disconnect_timeout=60
|
@ -1,27 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7
|
||||
Lbt3q5Knz8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeK
|
||||
FZM0Gp/WhSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0
|
||||
aAMqJEm88jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQ
|
||||
pRUWqZeJY4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAe
|
||||
yAHsAwmaP8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABAoIBAD40x/RKoEKIyE8B
|
||||
D6g0pB1EQo+CePFoN3SYewO1uR4WgtVmtxWVoa7r5BpdZGLe3uCWhpMX7z7W6bGs
|
||||
f1LFQOckjkHIfMIfTGfecRjO5Yqu+Pbxtq+gUah+S/plJr3IzdC+SUVNvzBnBMeX
|
||||
eU3Vmg2UQ2nQ+9GWu8D/c/vDwxx0X8oQ2G8QaxX0tUurlSMNA3M7xySwEvhx54fO
|
||||
UrDF3Q4yF48eA4butxVLFWf3cnlY+nR8uYd2vKfmp689/8C6kkfoM9igB78e93sm
|
||||
uDM2eRLm4kU5WLl301T42n6AF7w8J0MhLLVOIeLs4l5gZPa3uKvYFmuHQao7e/5R
|
||||
U/jHKrECgYEA8alPXuxFSVOvdhIsSN//Frj9CdExVdYmaLkt/2LO4FMnOaWh1xh7
|
||||
5iCY1bJT8D9dhfbqRg3qW2oguZD8gu04R8fTRegQ89qmAIwsEYqVf9salR41lZU4
|
||||
Rc+5yc7O11WIe9Lzu+ONFBFkAh3UFMR4zVZ/JhKIG/P5Srm7SUdKW2cCgYEA5aHo
|
||||
x2LR+yKhjkrBzHG3Qrfy1PtlYHjOpYYAKHQcBFuiG08W3CK/vkYl+mhv0uyhT7mn
|
||||
q6NDqrpZPRnDlOoEqgRS1X/QWKN6Pgd4HNLIawvp0vK9jYXDPcAXFzVthXCIwFcn
|
||||
3a3m4cHiuLdRNOHkydiHQyTOF6eEneN07TDvwvkCgYEApzOd1u9igPmFzQuF2GYi
|
||||
+HXFnaU/nUQuDwcQ7EJRIKRn31raPxiRoQesty5LJU6yRp4wOYgnPliPi9Tk4TGA
|
||||
XynC4/tMv2vorzhMxVY9Wdke602bhYNZC/RNd3O/aP2lEQdD3Bv04I2nxE8fDb9i
|
||||
VbAjCRSJV83WDf2zt1+78sECgYEAzezjRiKdcZu9y0/I+WEk2cUCE/MaF2he0FsZ
|
||||
uy1cjp/qAJltQ5452xUnK6cKWNlxU4CHF0mC/hC8xCldliZCZoEYE3PaUBLSJdwm
|
||||
35o6tpxpZI3gZJCG5NJlIp/8BkVDrVC7ZHV17hAkFEf4n/bPaB8wNYtE8jt8luaK
|
||||
TcarzGkCgYBn2alN0RLN2PHDurraFZB6GuCvh/arEjSCY3SDFQPF10CVjTDV7sx3
|
||||
eqJkwJ81syTmfJwZIceWbOFGgsuSx37UrQAVlHZSvzeqEg9dA5HqSoOACyidJI7j
|
||||
RG2+HB+KpsIZjGgLrEM4i7VOpYUDRdaouIXngFq/t9HNT+MDck5/Lw==
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtTCCAp2gAwIBAgIJAPXRSiP0Fs7sMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTcxMTA3MDg1MzQ2WhcNMjcxMTA1MDg1MzQ2WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7Lbt3q5Kn
|
||||
z8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeKFZM0Gp/W
|
||||
hSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0aAMqJEm8
|
||||
8jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQpRUWqZeJ
|
||||
Y4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAeyAHsAwma
|
||||
P8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABo4GnMIGkMB0GA1UdDgQWBBQdPc0R
|
||||
a8alY6Ab7voidkTGaH4PxzB1BgNVHSMEbjBsgBQdPc0Ra8alY6Ab7voidkTGaH4P
|
||||
x6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
|
||||
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPXRSiP0Fs7sMAwGA1UdEwQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAH1IZNkjuvt2nZPzXsuiVNyCE1vm346z
|
||||
naE0Uzt3aseAN9m/iiB8mLz+ryvWc2aFMX5lTdsHdm2rqmqBCBXeRwTLf4OeHIju
|
||||
ZQW6makWt6PxANEo6gbdPbQXbS420ssUhnR2irIH1SdI31iikVFPdiS0baRRE/gS
|
||||
+440M1jOOOnKm0Qin92ejsshmji/0qaD2+6D5TNw4HmIZaFTBw+kfjxCL6trfeBn
|
||||
4fT0RJ121V3G3+AtG5sWQ93B3pCg+jtD+fGKkNSLhphq84bD1Zv7l73QGOoylkEn
|
||||
Sc0ajTLOXFBb83yRdlgV3Da95jH9rDZ4jSod48m+KemoZTDQw0vSwAU=
|
||||
-----END CERTIFICATE-----
|
@ -1,21 +0,0 @@
|
||||
# nps
|
||||
 
|
||||
[](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://travis-ci.org/cnlh/nps)
|
||||
|
||||
nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**、**p2p等**,并带有功能强大的web管理端。
|
||||
|
||||
|
||||
## 背景
|
||||

|
||||
|
||||
1. 做微信公众号开发、小程序开发等----> 域名代理模式
|
||||
|
||||
|
||||
2. 想在外网通过ssh连接内网的机器,做云服务器到内网服务器端口的映射,----> tcp代理模式
|
||||
|
||||
3. 在非内网环境下使用内网dns,或者需要通过udp访问内网机器等----> udp代理模式
|
||||
|
||||
4. 在外网使用HTTP代理访问内网站点----> http代理模式
|
||||
|
||||
5. 搭建一个内网穿透ss,在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式
|
@ -1,16 +0,0 @@
|
||||

|
||||
|
||||
# NPS <small>0.26.10</small>
|
||||
|
||||
> 一款轻量级、高性能、功能强大的内网穿透代理服务器
|
||||
|
||||
- 几乎支持所有协议
|
||||
- 支持内网http代理、内网socks5代理、p2p等
|
||||
- 简洁但功能强大的WEB管理界面
|
||||
- 支持服务端、客户端同时控制
|
||||
- 扩展功能强大
|
||||
- 全平台兼容,一键注册为服务
|
||||
|
||||
|
||||
[GitHub](https://github.com/ehang-io/nps/)
|
||||
[开始使用](#nps)
|
@ -1,3 +0,0 @@
|
||||
* [](https://github.com/ehang-io/nps/stargazers)
|
||||
|
||||
* [](https://github.com/ehang-io/nps/network)
|
@ -1,29 +0,0 @@
|
||||
* 入门
|
||||
* [安装](install.md)
|
||||
* [启动](run.md)
|
||||
* [使用示例](example.md)
|
||||
* 服务端
|
||||
* [介绍](introduction.md)
|
||||
* [使用](nps_use.md)
|
||||
* [配置文件](server_config.md)
|
||||
* [增强功能](nps_extend.md)
|
||||
|
||||
* 客户端
|
||||
|
||||
* [基本使用](use.md)
|
||||
* [增强功能](npc_extend.md)
|
||||
|
||||
* 扩展
|
||||
|
||||
* [功能](feature.md)
|
||||
* [说明](description.md)
|
||||
* [web api](api.md)
|
||||
* [sdk](npc_sdk.md)
|
||||
|
||||
* 其他
|
||||
|
||||
* [FAQ](faq.md)
|
||||
* [贡献](contribute.md)
|
||||
* [捐助](donate.md)
|
||||
* [致谢](thanks.md)
|
||||
* [交流](discuss.md)
|
45
docs/api.md
@ -1,45 +0,0 @@
|
||||
# web api
|
||||
|
||||
需要开启请先去掉`nps.conf`中`auth_key`的注释并配置一个合适的密钥
|
||||
## webAPI验证说明
|
||||
- 采用auth_key的验证方式
|
||||
- 在提交的每个请求后面附带两个参数,`auth_key` 和`timestamp`
|
||||
|
||||
```
|
||||
auth_key的生成方式为:md5(配置文件中的auth_key+当前时间戳)
|
||||
```
|
||||
|
||||
```
|
||||
timestamp为当前时间戳
|
||||
```
|
||||
```
|
||||
curl --request POST \
|
||||
--url http://127.0.0.1:8080/client/list \
|
||||
--data 'auth_key=2a0000d9229e7dbcf79dd0f5e04bb084×tamp=1553045344&start=0&limit=10'
|
||||
```
|
||||
**注意:** 为保证安全,时间戳的有效范围为20秒内,所以每次提交请求必须重新生成。
|
||||
|
||||
## 获取服务端时间
|
||||
由于服务端与api请求的客户端时间差异不能太大,所以提供了一个可以获取服务端时间的接口
|
||||
|
||||
```
|
||||
POST /auth/gettime
|
||||
```
|
||||
|
||||
## 获取服务端authKey
|
||||
|
||||
如果想获取authKey,服务端提供获取authKey的接口
|
||||
|
||||
```
|
||||
POST /auth/getauthkey
|
||||
```
|
||||
将返回加密后的authKey,采用aes cbc加密,请使用与服务端配置文件中cryptKey相同的密钥进行解密
|
||||
|
||||
**注意:** nps配置文件中`auth_crypt_key`需为16位
|
||||
- 解密密钥长度128
|
||||
- 偏移量与密钥相同
|
||||
- 补码方式pkcs5padding
|
||||
- 解密串编码方式 十六进制
|
||||
|
||||
## 详细文档
|
||||
- **[详见](webapi.md)** (感谢@avengexyz)
|
@ -1,6 +0,0 @@
|
||||
# 贡献
|
||||
|
||||
- 如果遇到bug可以直接提交至dev分支
|
||||
- 使用遇到问题可以通过issues反馈
|
||||
- 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支
|
||||
- 如果有新的功能特性反馈,可以通过issues或者qq群反馈
|
@ -1,30 +0,0 @@
|
||||
# 说明
|
||||
## 获取用户真实ip
|
||||
如需使用需要在`nps.conf`中设置`http_add_origin_header=true`
|
||||
|
||||
在域名代理模式中,可以通过request请求 header 中的 X-Forwarded-For 和 X-Real-IP 来获取用户真实 IP。
|
||||
|
||||
**本代理前会在每一个http(s)请求中添加了这两个 header。**
|
||||
|
||||
## 热更新支持
|
||||
对于绝大多数配置,在web管理中的修改将实时使用,无需重启客户端或者服务端
|
||||
|
||||
## 客户端地址显示
|
||||
在web管理中将显示客户端的连接地址
|
||||
|
||||
## 流量统计
|
||||
可统计显示每个代理使用的流量,由于压缩和加密等原因,会和实际环境中的略有差异
|
||||
|
||||
## 当前客户端带宽
|
||||
可统计每个客户端当前的带宽,可能和实际有一定差异,仅供参考。
|
||||
|
||||
## 客户端与服务端版本对比
|
||||
为了程序正常运行,客户端与服务端的核心版本必须一致,否则将导致客户端无法成功连接致服务端。
|
||||
|
||||
## Linux系统限制
|
||||
默认情况下linux对连接数量有限制,对于性能好的机器完全可以调整内核参数以处理更多的连接。
|
||||
`tcp_max_syn_backlog` `somaxconn`
|
||||
酌情调整参数,增强网络性能
|
||||
|
||||
## web管理保护
|
||||
当一个ip连续登陆失败次数超过10次,将在一分钟内禁止该ip再次尝试。
|
@ -1,3 +0,0 @@
|
||||
# 交流群
|
||||
|
||||

|
@ -1,7 +0,0 @@
|
||||
# 捐助
|
||||
如果您觉得nps对你有帮助,欢迎给予我们一定捐助,也是帮助nps更好的发展。
|
||||
|
||||
## 支付宝
|
||||

|
||||
## 微信
|
||||

|
126
docs/example.md
@ -1,126 +0,0 @@
|
||||
# 使用示例
|
||||
## 统一准备工作(必做)
|
||||
- 开启服务端,假设公网服务器ip为1.1.1.1,配置文件中`bridge_port`为8024,配置文件中`web_port`为8080
|
||||
- 访问1.1.1.1:8080
|
||||
- 在客户端管理中创建一个客户端,记录下验证密钥
|
||||
- 内网客户端运行(windows使用cmd运行加.exe)
|
||||
|
||||
```shell
|
||||
./npc -server=1.1.1.1:8024 -vkey=客户端的密钥
|
||||
```
|
||||
**注意:运行服务端后,请确保能从客户端设备上正常访问配置文件中所配置的`bridge_port`端口,telnet,netcat这类的来检查**
|
||||
|
||||
## 域名解析
|
||||
|
||||
**适用范围:** 小程序开发、微信公众号开发、产品演示
|
||||
|
||||
**注意:域名解析模式为http反向代理,不是dns服务器,在web上能够轻松灵活配置**
|
||||
|
||||
**假设场景:**
|
||||
- 有一个域名proxy.com,有一台公网机器ip为1.1.1.1
|
||||
- 两个内网开发站点127.0.0.1:81,127.0.0.1:82
|
||||
- 想通过(http|https://)a.proxy.com访问127.0.0.1:81,通过(http|https://)b.proxy.com访问127.0.0.1:82
|
||||
|
||||
**使用步骤**
|
||||
- 将*.proxy.com解析到公网服务器1.1.1.1
|
||||
- 点击刚才创建的客户端的域名管理,添加两条规则规则:1、域名:`a.proxy.com`,内网目标:`127.0.0.1:81`,2、域名:`b.proxy.com`,内网目标:`127.0.0.1:82`
|
||||
|
||||
现在访问(http|https://)`a.proxy.com`,`b.proxy.com`即可成功
|
||||
|
||||
**https:** 如需使用https请进行相关配置,详见 [使用https](/nps_extend?id=使用https)
|
||||
|
||||
## tcp隧道
|
||||
|
||||
|
||||
**适用范围:** ssh、远程桌面等tcp连接场景
|
||||
|
||||
**假设场景:**
|
||||
想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接
|
||||
|
||||
**使用步骤**
|
||||
- 在刚才创建的客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),保存。
|
||||
- 访问公网服务器ip(1.1.1.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@1.1.1.1`
|
||||
|
||||
## udp隧道
|
||||
|
||||
**适用范围:** 内网dns解析等udp连接场景
|
||||
|
||||
**假设场景:**
|
||||
内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1
|
||||
|
||||
**使用步骤**
|
||||
- 在刚才创建的客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),保存。
|
||||
- 修改需要使用的dns地址为1.1.1.1,则相当于使用10.1.50.102作为dns服务器
|
||||
|
||||
## socks5代理
|
||||
|
||||
|
||||
**适用范围:** 在外网环境下如同使用vpn一样访问内网设备或者资源
|
||||
|
||||
**假设场景:**
|
||||
想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果
|
||||
|
||||
**使用步骤**
|
||||
- 在刚才创建的客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),保存。
|
||||
- 在外网环境的本机配置socks5代理(例如使用proxifier进行全局代理),ip为公网服务器ip(1.1.1.1),端口为填写的监听端口(8003),即可畅享内网了
|
||||
|
||||
**注意**
|
||||
经过socks5代理,当收到socks5数据包时socket已经是accept状态。表现是扫描端口全open,建立连接后短时间关闭。若想同内网表现一致,建议远程连接一台设备。
|
||||
|
||||
## http正向代理
|
||||
|
||||
**适用范围:** 在外网环境下使用http正向代理访问内网站点
|
||||
|
||||
**假设场景:**
|
||||
想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站
|
||||
|
||||
**使用步骤**
|
||||
|
||||
- 在刚才创建的客户端隧道管理中添加一条http代理,填写监听的端口(8004),保存。
|
||||
- 在外网环境的本机配置http代理,ip为公网服务器ip(1.1.1.1),端口为填写的监听端口(8004),即可访问了
|
||||
|
||||
**注意:对于私密代理与p2p,除了统一配置的客户端和服务端,还需要一个客户端作为访问端提供一个端口来访问**
|
||||
|
||||
## 私密代理
|
||||
|
||||
**适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的tcp服务,例如ssh。
|
||||
|
||||
**假设场景:**
|
||||
无需新增多的端口实现访问内网服务器10.1.50.2的22端口
|
||||
|
||||
**使用步骤**
|
||||
- 在刚才创建的客户端中添加一条私密代理,并设置唯一密钥secrettest和内网目标10.1.50.2:22
|
||||
- 在需要连接ssh的机器上以执行命令
|
||||
|
||||
```
|
||||
./npc -server=1.1.1.1:8024 -vkey=vkey -type=tcp -password=secrettest -local_type=secret
|
||||
```
|
||||
如需指定本地端口可加参数`-local_port=xx`,默认为2000
|
||||
|
||||
**注意:** password为web管理上添加的唯一密钥,具体命令可查看web管理上的命令提示
|
||||
|
||||
假设10.1.50.2用户名为root,现在执行`ssh -p 2000 root@127.0.0.1`即可访问ssh
|
||||
|
||||
|
||||
## p2p服务
|
||||
|
||||
**适用范围:** 大流量传输场景,流量不经过公网服务器,但是由于p2p穿透和nat类型关系较大,不保证100%成功,支持大部分nat类型。[nat类型检测](/npc_extend?id=nat类型检测)
|
||||
|
||||
**假设场景:**
|
||||
|
||||
想通过访问使用端机器(访问端,也就是本机)的2000端口---->访问到内网机器 10.2.50.2的22端口
|
||||
|
||||
**使用步骤**
|
||||
- 在`nps.conf`中设置`p2p_ip`(nps服务器ip)和`p2p_port`(nps服务器udp端口)
|
||||
> 注:若 `p2p_port` 设置为6000,请在防火墙开放6000~6002(额外添加2个端口)udp端口
|
||||
- 在刚才刚才创建的客户端中添加一条p2p代理,并设置唯一密钥p2pssh
|
||||
- 在使用端机器(本机)执行命令
|
||||
|
||||
```
|
||||
./npc -server=1.1.1.1:8024 -vkey=123 -password=p2pssh -target=10.2.50.2:22
|
||||
```
|
||||
如需指定本地端口可加参数`-local_port=xx`,默认为2000
|
||||
|
||||
**注意:** password为web管理上添加的唯一密钥,具体命令可查看web管理上的命令提示
|
||||
|
||||
假设内网机器为10.2.50.2的ssh用户名为root,现在在本机上执行`ssh -p 2000 root@127.0.0.1`即可访问机器2的ssh,如果是网站在浏览器访问127.0.0.1:2000端口即可。
|
20
docs/faq.md
@ -1,20 +0,0 @@
|
||||
# FAQ
|
||||
|
||||
- 服务端无法启动
|
||||
```
|
||||
服务端默认配置启用了8024,8080,80,443端口,端口冲突无法启动,请修改配置
|
||||
```
|
||||
- 客户端无法连接服务端
|
||||
```
|
||||
请检查配置文件中的所有端口是否在安全组,防火墙放行
|
||||
请检查vkey是否对应
|
||||
请检查版本是否对应
|
||||
```
|
||||
- 服务端配置文件修改无效
|
||||
```
|
||||
install 之后,Linux 配置文件在 /etc/nps
|
||||
```
|
||||
- p2p穿透失败 [p2p服务](https://ehang-io.github.io/nps/#/example?id=p2p%e6%9c%8d%e5%8a%a1)
|
||||
```
|
||||
双方nat类型都是Symmetric Nat一定不成功,建议先查看nat类型。请按照文档操作(标题上有超链接)
|
||||
```
|
254
docs/feature.md
@ -1,254 +0,0 @@
|
||||
# 扩展功能
|
||||
## 缓存支持
|
||||
对于web站点来说,一些静态文件往往消耗更大的流量,且在内网穿透中,静态文件还需到客户端获取一次,这将导致更大的流量消耗。nps在域名解析代理中支持对静态文件进行缓存。
|
||||
|
||||
即假设一个站点有a.css,nps将只需从npc客户端读取一次该文件,然后把该文件的内容放在内存中,下一次将不再对npc客户端进行请求而直接返回内存中的对应内容。该功能默认是关闭的,如需开启请在`nps.conf`中设置`http_cache=true`,并设置`http_cache_length`(缓存文件的个数,消耗内存,不宜过大,0表示不限制个数)
|
||||
|
||||
## 数据压缩支持
|
||||
|
||||
由于是内网穿透,内网客户端与服务端之间的隧道存在大量的数据交换,为节省流量,加快传输速度,由此本程序支持SNNAPY形式的压缩。
|
||||
|
||||
|
||||
- 所有模式均支持数据压缩
|
||||
- 在web管理或客户端配置文件中设置
|
||||
|
||||
|
||||
## 加密传输
|
||||
|
||||
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了ssh协议等,通过设置 配置文件,将服务端与客户端之间的通信内容加密传输,将会有效防止流量被拦截。
|
||||
- nps现在默认每次启动时随机生成tls证书,用于加密传输
|
||||
|
||||
|
||||
|
||||
## 站点保护
|
||||
域名代理模式所有客户端共用一个http服务端口,在知道域名后任何人都可访问,一些开发或者测试环境需要保密,所以可以设置用户名和密码,nps将通过 Http Basic Auth 来保护,访问时需要输入正确的用户名和密码。
|
||||
|
||||
|
||||
- 在web管理或客户端配置文件中设置
|
||||
|
||||
## host修改
|
||||
|
||||
由于内网站点需要的host可能与公网域名不一致,域名代理支持host修改功能,即修改request的header中的host字段。
|
||||
|
||||
**使用方法:在web管理中设置**
|
||||
|
||||
## 自定义header
|
||||
|
||||
支持对header进行新增或者修改,以配合服务的需要
|
||||
|
||||
## 404页面配置
|
||||
支持域名解析模式的自定义404页面,修改/web/static/page/error.html中内容即可,暂不支持静态文件等内容
|
||||
|
||||
## 流量限制
|
||||
|
||||
支持客户端级流量限制,当该客户端入口流量与出口流量达到设定的总量后会拒绝服务
|
||||
,域名代理会返回404页面,其他代理会拒绝连接,使用该功能需要在`nps.conf`中设置`allow_flow_limit`,默认是关闭的。
|
||||
|
||||
## 带宽限制
|
||||
|
||||
支持客户端级带宽限制,带宽计算方式为入口和出口总和,权重均衡,使用该功能需要在`nps.conf`中设置`allow_rate_limit`,默认是关闭的。
|
||||
|
||||
## 负载均衡
|
||||
本代理支持域名解析模式和tcp代理的负载均衡,在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡
|
||||
|
||||
## 端口白名单
|
||||
为了防止服务端上的端口被滥用,可在nps.conf中配置allow_ports限制可开启的端口,忽略或者不填表示端口不受限制,格式:
|
||||
|
||||
```ini
|
||||
allow_ports=9001-9009,10001,11000-12000
|
||||
```
|
||||
|
||||
## 端口范围映射
|
||||
当客户端以配置文件的方式启动时,可以将本地的端口进行范围映射,仅支持tcp和udp模式,例如:
|
||||
|
||||
```ini
|
||||
[tcp]
|
||||
mode=tcp
|
||||
server_port=9001-9009,10001,11000-12000
|
||||
target_port=8001-8009,10002,13000-14000
|
||||
```
|
||||
|
||||
逗号分隔,可单个或者范围,注意上下端口的对应关系,无法一一对应将不能成功
|
||||
## 端口范围映射到其他机器
|
||||
```ini
|
||||
[tcp]
|
||||
mode=tcp
|
||||
server_port=9001-9009,10001,11000-12000
|
||||
target_port=8001-8009,10002,13000-14000
|
||||
target_ip=10.1.50.2
|
||||
```
|
||||
填写target_ip后则表示映射的该地址机器的端口,忽略则便是映射本地127.0.0.1,仅范围映射时有效
|
||||
|
||||
## KCP协议支持
|
||||
|
||||
在网络质量非常好的情况下,例如专线,内网,可以开启略微降低延迟。如需使用可在nps.conf中修改`bridge_type`为kcp
|
||||
,设置后本代理将开启udp端口(`bridge_port`)
|
||||
|
||||
注意:当服务端为kcp时,客户端连接时也需要使用相同配置,无配置文件模式加上参数type=kcp,配置文件模式在配置文件中设置tp=kcp
|
||||
|
||||
## 域名泛解析
|
||||
支持域名泛解析,例如将host设置为*.proxy.com,a.proxy.com、b.proxy.com等都将解析到同一目标,在web管理中或客户端配置文件中将host设置为此格式即可。
|
||||
|
||||
## URL路由
|
||||
本代理支持根据URL将同一域名转发到不同的内网服务器,可在web中或客户端配置文件中设置,此参数也可忽略,例如在客户端配置文件中
|
||||
|
||||
```ini
|
||||
[web1]
|
||||
host=a.proxy.com
|
||||
target_addr=127.0.0.1:7001
|
||||
location=/test
|
||||
[web2]
|
||||
host=a.proxy.com
|
||||
target_addr=127.0.0.1:7002
|
||||
location=/static
|
||||
```
|
||||
对于`a.proxy.com/test`将转发到`web1`,对于`a.proxy.com/static`将转发到`web2`
|
||||
|
||||
## 限制ip访问
|
||||
如果将一些危险性高的端口例如ssh端口暴露在公网上,可能会带来一些风险,本代理支持限制ip访问。
|
||||
|
||||
**使用方法:** 在配置文件nps.conf中设置`ip_limit`=true,设置后仅通过注册的ip方可访问。
|
||||
|
||||
**ip注册**:
|
||||
|
||||
**方式一:**
|
||||
在需要访问的机器上,运行客户端
|
||||
|
||||
```
|
||||
./npc register -server=ip:port -vkey=公钥或客户端密钥 time=2
|
||||
```
|
||||
|
||||
time为有效小时数,例如time=2,在当前时间后的两小时内,本机公网ip都可以访问nps代理.
|
||||
|
||||
**方式二:**
|
||||
此外nps的web登陆也可提供验证的功能,成功登陆nps web admin后将自动为登陆的ip注册两小时的允许访问权限。
|
||||
|
||||
|
||||
**注意:** 本机公网ip并不是一成不变的,请自行注意有效期的设置,同时同一网络下,多人也可能是在公用同一个公网ip。
|
||||
## 客户端最大连接数
|
||||
为防止恶意大量长连接,影响服务端程序的稳定性,可以在web或客户端配置文件中为每个客户端设置最大连接数。该功能针对`socks5`、`http正向代理`、`域名代理`、`tcp代理`、`udp代理`、`私密代理`生效,使用该功能需要在`nps.conf`中设置`allow_connection_num_limit=true`,默认是关闭的。
|
||||
|
||||
## 客户端最大隧道数限制
|
||||
nps支持对客户端的隧道数量进行限制,该功能默认是关闭的,如需开启,请在`nps.conf`中设置`allow_tunnel_num_limit=true`。
|
||||
## 端口复用
|
||||
在一些严格的网络环境中,对端口的个数等限制较大,nps支持强大端口复用功能。将`bridge_port`、 `http_proxy_port`、 `https_proxy_port` 、`web_port`都设置为同一端口,也能正常使用。
|
||||
|
||||
- 使用时将需要复用的端口设置为与`bridge_port`一致即可,将自动识别。
|
||||
- 如需将web管理的端口也复用,需要配置`web_host`也就是一个二级域名以便区分
|
||||
|
||||
## 多路复用
|
||||
|
||||
nps主要通信默认基于多路复用,无需开启。
|
||||
|
||||
多路复用基于TCP滑动窗口原理设计,动态计算延迟以及带宽来算出应该往网络管道中打入的流量。
|
||||
由于主要通信大多采用TCP协议,并无法探测其实时丢包情况,对于产生丢包重传的情况,采用较大的宽容度,
|
||||
5分钟的等待时间,超时将会关闭当前隧道连接并重新建立,这将会抛弃当前所有的连接。
|
||||
在Linux上,可以通过调节内核参数来适应不同应用场景。
|
||||
|
||||
对于需求大带宽又有一定的丢包的场景,可以保持默认参数不变,尽可能少抛弃连接
|
||||
高并发下可根据[Linux系统限制](## Linux系统限制) 调整
|
||||
|
||||
对于延迟敏感而又有一定丢包的场景,可以适当调整TCP重传次数
|
||||
`tcp_syn_retries`, `tcp_retries1`, `tcp_retries2`
|
||||
高并发同上
|
||||
nps会在系统主动关闭连接的时候拿到报错,进而重新建立隧道连接
|
||||
|
||||
## 环境变量渲染
|
||||
npc支持环境变量渲染以适应在某些特殊场景下的要求。
|
||||
|
||||
**在无配置文件启动模式下:**
|
||||
设置环境变量
|
||||
```
|
||||
export NPC_SERVER_ADDR=1.1.1.1:8024
|
||||
export NPC_SERVER_VKEY=xxxxx
|
||||
```
|
||||
直接执行./npc即可运行
|
||||
|
||||
**在配置文件启动模式下:**
|
||||
```ini
|
||||
[common]
|
||||
server_addr={{.NPC_SERVER_ADDR}}
|
||||
conn_type=tcp
|
||||
vkey={{.NPC_SERVER_VKEY}}
|
||||
auto_reconnection=true
|
||||
[web]
|
||||
host={{.NPC_WEB_HOST}}
|
||||
target_addr={{.NPC_WEB_TARGET}}
|
||||
```
|
||||
在配置文件中填入相应的环境变量名称,npc将自动进行渲染配置文件替换环境变量
|
||||
|
||||
## 健康检查
|
||||
|
||||
当客户端以配置文件模式启动时,支持多节点的健康检查。配置示例如下
|
||||
|
||||
```ini
|
||||
[health_check_test1]
|
||||
health_check_timeout=1
|
||||
health_check_max_failed=3
|
||||
health_check_interval=1
|
||||
health_http_url=/
|
||||
health_check_type=http
|
||||
health_check_target=127.0.0.1:8083,127.0.0.1:8082
|
||||
|
||||
[health_check_test2]
|
||||
health_check_timeout=1
|
||||
health_check_max_failed=3
|
||||
health_check_interval=1
|
||||
health_check_type=tcp
|
||||
health_check_target=127.0.0.1:8083,127.0.0.1:8082
|
||||
```
|
||||
**health关键词必须在开头存在**
|
||||
|
||||
第一种是http模式,也就是以get的方式请求目标+url,返回状态码为200表示成功
|
||||
|
||||
第一种是tcp模式,也就是以tcp的方式与目标建立连接,能成功建立连接表示成功
|
||||
|
||||
如果失败次数超过`health_check_max_failed`,nps则会移除该npc下的所有该目标,如果失败后目标重新上线,nps将自动将目标重新加入。
|
||||
|
||||
项 | 含义
|
||||
---|---
|
||||
health_check_timeout | 健康检查超时时间
|
||||
health_check_max_failed | 健康检查允许失败次数
|
||||
health_check_interval | 健康检查间隔
|
||||
health_check_type | 健康检查类型
|
||||
health_check_target | 健康检查目标,多个以逗号(,)分隔
|
||||
health_check_type | 健康检查类型
|
||||
health_http_url | 健康检查url,仅http模式适用
|
||||
|
||||
## 日志输出
|
||||
|
||||
日志输出级别
|
||||
|
||||
**对于npc:**
|
||||
```
|
||||
-log_level=0~7 -log_path=npc.log
|
||||
```
|
||||
```
|
||||
LevelEmergency->0 LevelAlert->1
|
||||
|
||||
LevelCritical->2 LevelError->3
|
||||
|
||||
LevelWarning->4 LevelNotice->5
|
||||
|
||||
LevelInformational->6 LevelDebug->7
|
||||
```
|
||||
默认为全输出,级别为0到7
|
||||
|
||||
**对于nps:**
|
||||
|
||||
在`nps.conf`中设置相关配置即可
|
||||
|
||||
## pprof性能分析与调试
|
||||
|
||||
可在服务端与客户端配置中开启pprof端口,用于性能分析与调试,注释或留空相应参数为关闭。
|
||||
|
||||
默认为关闭状态
|
||||
|
||||
## 自定义客户端超时检测断开时间
|
||||
|
||||
客户端与服务端间会间隔5s相互发送延迟测量包,这个时间间隔不可修改。
|
||||
可修改延迟测量包丢包的次数,默认为60也就是5分钟都收不到一个延迟测量回包,则会断开客户端连接。
|
||||
值得注意的是需要客户端的socket关闭,才会进行重连,也就是当客户端无法收到服务端的fin包时,只有客户端自行关闭socket才行。
|
||||
也就是假如服务端设置为较低值,而客户端设置较高值,而此时服务端断开连接而客户端无法收到服务端的fin包,客户端也会继续等着直到触发客户端的超时设置。
|
||||
|
||||
在`nps.conf`或`npc.conf`中设置`disconnect_timeout`即可,客户端还可附带`-disconnect_timeout=60`参数启动
|
@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Document</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
<meta name="description" content="Description">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="//unpkg.com/docsify-edit-on-github/index.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
window.$docsify = {
|
||||
name: '',
|
||||
repo: '',
|
||||
loadSidebar: true,
|
||||
coverpage: true,
|
||||
loadNavbar: true,
|
||||
subMaxLevel: 2,
|
||||
maxLevel: 4,
|
||||
search: {
|
||||
noData: "没有结果",
|
||||
paths: 'auto',
|
||||
placeholder: "搜索",
|
||||
hideOtherSidebarContent: true, // whether or not to hide other sidebar content
|
||||
},
|
||||
plugins: [
|
||||
EditOnGithubPlugin.create("https://github.com/ehang-io/nps/tree/master/docs/", "", "在github上编辑"),
|
||||
]
|
||||
|
||||
}
|
||||
</script>
|
||||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//unpkg.com/docsify-copy-code"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,18 +0,0 @@
|
||||
# 安装
|
||||
## 安装包安装
|
||||
[releases](https://github.com/ehang-io/nps/releases)
|
||||
|
||||
下载对应的系统版本即可,服务端和客户端是单独的
|
||||
|
||||
## 源码安装
|
||||
- 安装源码
|
||||
```go get -u ehang.io/nps```
|
||||
- 编译
|
||||
|
||||
服务端```go build cmd/nps/nps.go```
|
||||
|
||||
客户端```go build cmd/npc/npc.go```
|
||||
|
||||
## docker安装
|
||||
> [server](https://hub.docker.com/r/ffdfgdfg/nps)
|
||||
> [client](https://hub.docker.com/r/ffdfgdfg/npc)
|
@ -1,4 +0,0 @@
|
||||

|
||||
# 介绍
|
||||
|
||||
可在网页上配置和管理各个tcp、udp隧道、内网站点代理,http、https解析等,功能强大,操作方便。
|
BIN
docs/logo.png
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576165662444" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="50156" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M414.764 498.138c-4.094-8.188-13-12.844-22.094-11.562L181.58 516.73a21.496 21.496 0 0 0-12.062 6.032L48.926 643.4c-8.344 8.344-8.344 21.842 0 30.154l90.436 90.466c8.344 8.312 21.844 8.312 30.156 0l241.246-241.26a21.344 21.344 0 0 0 4-24.622zM507.262 842.428l30.156-211.09c1.312-9.094-3.376-17.968-11.562-22.094a21.34 21.34 0 0 0-24.624 4L259.984 854.49c-8.312 8.312-8.312 21.812 0 30.156l90.468 90.464c8.344 8.312 21.842 8.312 30.156 0l120.624-120.622a21.214 21.214 0 0 0 6.03-12.06z" fill="#DA4453" p-id="50157"></path><path d="M732.444 186.238a21.254 21.254 0 0 0-14.75-15.156l-93.778-26.876a21.288 21.288 0 0 0-20.938 5.438l-87.188 87.154a21.266 21.266 0 0 0-5.656 19.968 21.088 21.088 0 0 0 5.656 10.188 21.33 21.33 0 0 0 8.344 5.156l90.468 30.156a21.27 21.27 0 0 0 21.812-5.156l90.464-90.468a21.252 21.252 0 0 0 5.566-20.404zM879.786 400.078l-26.842-93.75a21.348 21.348 0 0 0-15.156-14.78 21.388 21.388 0 0 0-20.438 5.562l-90.468 90.466a21.348 21.348 0 0 0-5.156 21.812l30.156 90.466a21.33 21.33 0 0 0 5.156 8.344 21.4 21.4 0 0 0 10.188 5.688 21.356 21.356 0 0 0 19.968-5.688l87.186-87.156a21.39 21.39 0 0 0 5.406-20.964z" fill="#434A54" p-id="50158"></path><path d="M997.284 138.206c30.312-61.56 34.876-103.592 13.5-124.966-21.376-21.374-63.438-16.842-124.998 13.5-47.06 23.188-97.592 58.28-128.748 89.436L6.27 866.99c-4 4-6.25 9.406-6.25 15.062s2.25 11.094 6.25 15.094l120.624 120.59c8.312 8.344 21.812 8.344 30.156 0l750.768-750.782c31.154-31.156 66.278-81.686 89.466-128.748z" fill="#E6E9ED" p-id="50159"></path><path d="M907.816 266.954c31.154-31.156 66.28-81.686 89.466-128.748 30.312-61.56 34.876-103.592 13.5-124.966-21.376-21.374-63.438-16.842-124.998 13.5-47.06 23.188-97.592 58.28-128.748 89.436l150.78 150.778z" fill="#ED5564" p-id="50160"></path><path d="M157.048 1017.736l15.062-15.062-150.778-150.778-15.062 15.094c-4 4-6.25 9.406-6.25 15.062s2.25 11.094 6.25 15.094l120.624 120.59c8.312 8.344 21.81 8.344 30.154 0z" fill="#FFCE54" p-id="50161"></path><path d="M81.662 972.546l-30.156-30.156 30.156-30.154L111.814 942.39z" fill="#F6BB42" p-id="50162"></path><path d="M199.672 824.334c-8.312-8.344-8.312-21.844 0-30.156l241.246-241.246c8.344-8.312 21.844-8.312 30.156 0 8.344 8.344 8.344 21.844 0 30.156L229.828 824.334c-8.312 8.312-21.812 8.312-30.156 0z" fill="#ED5564" p-id="50163"></path><path d="M787.194 236.798c-8.312-8.312-21.812-8.312-30.156 0l-120.622 120.624c-8.312 8.344-8.312 21.844 0 30.156 8.342 8.344 21.842 8.344 30.154 0l120.624-120.624c8.344-8.312 8.344-21.812 0-30.156z" fill="#656D78" p-id="50164"></path><path d="M997.284 138.206c30.312-61.56 34.876-103.592 13.5-124.966a40.728 40.728 0 0 0-8.562-6.562c13.376 23.156 6.688 62.25-20.032 116.466-23.188 47.062-58.312 97.592-89.436 128.748L141.954 1002.674c-8.312 8.312-21.812 8.312-30.156 0l-60.312-60.31-45.216-45.248v0.032l120.624 120.59c8.312 8.344 21.812 8.344 30.156 0l750.768-750.782c31.154-31.158 66.278-81.688 89.466-128.75z" fill="#FFFFFF" opacity=".2" p-id="50165"></path></svg>
|
Before Width: | Height: | Size: 3.3 KiB |
@ -1,36 +0,0 @@
|
||||
# 增强功能
|
||||
## nat类型检测
|
||||
```
|
||||
./npc nat -stun_addr=stun.stunprotocol.org:3478
|
||||
```
|
||||
如果p2p双方都是Symmetric Nat,肯定不能成功,其他组合都有较大成功率。`stun_addr`可以指定stun服务器地址。
|
||||
## 状态检查
|
||||
```
|
||||
./npc status -config=npc配置文件路径
|
||||
```
|
||||
## 重载配置文件
|
||||
```
|
||||
./npc restart -config=npc配置文件路径
|
||||
```
|
||||
|
||||
## 通过代理连接nps
|
||||
有时候运行npc的内网机器无法直接访问外网,此时可以可以通过socks5代理连接nps
|
||||
|
||||
对于配置文件方式启动,设置
|
||||
```ini
|
||||
[common]
|
||||
proxy_url=socks5://111:222@127.0.0.1:8024
|
||||
```
|
||||
对于无配置文件模式,加上参数
|
||||
|
||||
```
|
||||
-proxy=socks5://111:222@127.0.0.1:8024
|
||||
```
|
||||
支持socks5和http两种模式
|
||||
|
||||
即socks5://username:password@ip:port
|
||||
|
||||
或http://username:password@ip:port
|
||||
|
||||
## 群晖支持
|
||||
可在releases中下载spk群晖套件,例如`npc_x64-6.1_0.19.0-1.spk`
|
@ -1,24 +0,0 @@
|
||||
# npc sdk文档
|
||||
|
||||
```
|
||||
命令行模式启动客户端
|
||||
从v0.26.10开始,此函数会阻塞,直到客户端退出返回,请自行管理是否重连
|
||||
p0->连接地址
|
||||
p1->vkey
|
||||
p2->连接类型(tcp or udp)
|
||||
p3->连接代理
|
||||
|
||||
extern GoInt StartClientByVerifyKey(char* p0, char* p1, char* p2, char* p3);
|
||||
|
||||
查看当前启动的客户端状态,在线为1,离线为0
|
||||
extern GoInt GetClientStatus();
|
||||
|
||||
关闭客户端
|
||||
extern void CloseClient();
|
||||
|
||||
获取当前客户端版本
|
||||
extern char* Version();
|
||||
|
||||
获取日志,实时更新
|
||||
extern char* Logs();
|
||||
```
|
@ -1,107 +0,0 @@
|
||||
# 增强功能
|
||||
## 使用https
|
||||
|
||||
**方式一:** 类似于nginx实现https的处理
|
||||
|
||||
在配置文件中将https_proxy_port设置为443或者其他你想配置的端口,将`https_just_proxy`设置为false,nps 重启后,在web管理界面,域名新增或修改界面中修改域名证书和密钥。
|
||||
|
||||
**此外:** 可以在`nps.conf`中设置一个默认的https配置,当遇到未在web中设置https证书的域名解析时,将自动使用默认证书,另还有一种情况就是对于某些请求的clienthello不携带sni扩展信息,nps也将自动使用默认证书
|
||||
|
||||
|
||||
**方式二:** 在内网对应服务器上设置https
|
||||
|
||||
在`nps.conf`中将`https_just_proxy`设置为true,并且打开`https_proxy_port`端口,然后nps将直接转发https请求到内网服务器上,由内网服务器进行https处理
|
||||
|
||||
## 与nginx配合
|
||||
|
||||
有时候我们还需要在云服务器上运行nginx来保证静态文件缓存等,本代理可和nginx配合使用,在配置文件中将httpProxyPort设置为非80端口,并在nginx中配置代理,例如httpProxyPort为8010时
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name *.proxy.com;
|
||||
location / {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://127.0.0.1:8010;
|
||||
}
|
||||
}
|
||||
```
|
||||
如需使用https也可在nginx监听443端口并配置ssl,并将本代理的httpsProxyPort设置为空关闭https即可,例如httpProxyPort为8020时
|
||||
|
||||
```
|
||||
server {
|
||||
listen 443;
|
||||
server_name *.proxy.com;
|
||||
ssl on;
|
||||
ssl_certificate certificate.crt;
|
||||
ssl_certificate_key private.key;
|
||||
ssl_session_timeout 5m;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
location / {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
}
|
||||
}
|
||||
```
|
||||
## web管理使用https
|
||||
如果web管理需要使用https,可以在配置文件`nps.conf`中设置`web_open_ssl=true`,并配置`web_cert_file`和`web_key_file`
|
||||
## web使用Caddy代理
|
||||
|
||||
如果将web配置到Caddy代理,实现子路径访问nps,可以这样配置.
|
||||
|
||||
假设我们想通过 `http://caddy_ip:caddy_port/nps` 来访问后台, Caddyfile 这样配置:
|
||||
|
||||
```Caddyfile
|
||||
caddy_ip:caddy_port/nps {
|
||||
##server_ip 为 nps 服务器IP
|
||||
##web_port 为 nps 后台端口
|
||||
proxy / http://server_ip:web_port/nps {
|
||||
transparent
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
nps.conf 修改 `web_base_url` 为 `/nps` 即可
|
||||
```
|
||||
web_base_url=/nps
|
||||
```
|
||||
|
||||
|
||||
## 关闭代理
|
||||
|
||||
如需关闭http代理可在配置文件中将http_proxy_port设置为空,如需关闭https代理可在配置文件中将https_proxy_port设置为空。
|
||||
|
||||
## 流量数据持久化
|
||||
服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flow_store_interval`参数,单位为分钟
|
||||
|
||||
**注意:** nps不会持久化通过公钥连接的客户端
|
||||
## 系统信息显示
|
||||
nps服务端支持在web上显示和统计服务器的相关信息,但默认一些统计图表是关闭的,如需开启请在`nps.conf`中设置`system_info_display=true`
|
||||
|
||||
## 自定义客户端连接密钥
|
||||
web上可以自定义客户端连接的密钥,但是必须具有唯一性
|
||||
## 关闭公钥访问
|
||||
可以将`nps.conf`中的`public_vkey`设置为空或者删除
|
||||
|
||||
## 关闭web管理
|
||||
可以将`nps.conf`中的`web_port`设置为空或者删除
|
||||
|
||||
## 服务端多用户登陆
|
||||
如果将`nps.conf`中的`allow_user_login`设置为true,服务端web将支持多用户登陆,登陆用户名为user,默认密码为每个客户端的验证密钥,登陆后可以进入客户端编辑修改web登陆的用户名和密码,默认该功能是关闭的。
|
||||
|
||||
## 用户注册功能
|
||||
nps服务端支持用户注册功能,可将`nps.conf`中的`allow_user_register`设置为true,开启后登陆页将会有有注册功能,
|
||||
|
||||
## 监听指定ip
|
||||
|
||||
nps支持每个隧道监听不同的服务端端口,在`nps.conf`中设置`allow_multi_ip=true`后,可在web中控制,或者npc配置文件中(可忽略,默认为0.0.0.0)
|
||||
```ini
|
||||
server_ip=xxx
|
||||
```
|
||||
## 代理到服务端本地
|
||||
在使用nps监听80或者443端口时,默认是将所有的请求都会转发到内网上,但有时候我们的nps服务器的上一些服务也需要使用这两个端口,nps提供类似于`nginx` `proxy_pass` 的功能,支持将代理到服务器本地,该功能支持域名解析,tcp、udp隧道,默认关闭。
|
||||
|
||||
**即:** 假设在nps的vps服务器上有一个服务使用5000端口,这时候nps占用了80端口和443,我们想能使用一个域名通过http(s)访问到5000的服务。
|
||||
|
||||
**使用方式:** 在`nps.conf`中设置`allow_local_proxy=true`,然后在web上设置想转发的隧道或者域名然后选择转发到本地选项即可成功。
|
@ -1,47 +0,0 @@
|
||||
# 使用
|
||||
**提示:使用web模式时,服务端执行文件必须在项目根目录,否则无法正确加载配置文件**
|
||||
|
||||
## web管理
|
||||
|
||||
进入web界面,公网ip:web界面端口(默认8080),密码默认为123
|
||||
|
||||
进入web管理界面,有详细的说明
|
||||
|
||||
## 服务端配置文件重载
|
||||
对于linux、darwin
|
||||
```shell
|
||||
sudo nps reload
|
||||
```
|
||||
对于windows
|
||||
```shell
|
||||
nps.exe reload
|
||||
```
|
||||
**说明:** 仅支持部分配置重载,例如`allow_user_login` `auth_crypt_key` `auth_key` `web_username` `web_password` 等,未来将支持更多
|
||||
|
||||
|
||||
## 服务端停止或重启
|
||||
对于linux、darwin
|
||||
```shell
|
||||
sudo nps stop|restart
|
||||
```
|
||||
对于windows
|
||||
```shell
|
||||
nps.exe stop|restart
|
||||
```
|
||||
## 服务端更新
|
||||
请首先执行 `sudo nps stop` 或者 `nps.exe stop` 停止运行,然后
|
||||
|
||||
对于linux
|
||||
```shell
|
||||
sudo nps-update update
|
||||
```
|
||||
对于windows
|
||||
```shell
|
||||
nps-update.exe update
|
||||
```
|
||||
|
||||
更新完成后,执行执行 `sudo nps start` 或者 `nps.exe start` 重新运行即可完成升级
|
||||
|
||||
如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的nps二进制文件和web目录
|
||||
|
||||
注意:`nps install` 之后的 nps 不在原位置,请使用 `whereis nps` 查找具体目录覆盖 nps 二进制文件
|
42
docs/run.md
@ -1,42 +0,0 @@
|
||||
# 启动
|
||||
## 服务端
|
||||
下载完服务器压缩包后,解压,然后进入解压后的文件夹
|
||||
|
||||
- 执行安装命令
|
||||
|
||||
对于linux|darwin ```sudo ./nps install```
|
||||
|
||||
对于windows,管理员身份运行cmd,进入安装目录 ```nps.exe install```
|
||||
|
||||
- 启动
|
||||
|
||||
对于linux|darwin ```sudo nps start```
|
||||
|
||||
对于windows,管理员身份运行cmd,进入程序目录 ```nps.exe start```
|
||||
|
||||
```安装后windows配置文件位于 C:\Program Files\nps,linux和darwin位于/etc/nps```
|
||||
|
||||
停止和重启可用,stop和restart
|
||||
|
||||
**如果发现没有启动成功,可以使用`nps(.exe) stop`,然后运行`nps.(exe)`运行调试,或查看日志**(Windows日志文件位于当前运行目录下,linux和darwin位于/var/log/nps.log)
|
||||
- 访问服务端ip:web服务端口(默认为8080)
|
||||
- 使用用户名和密码登陆(默认admin/123,正式使用一定要更改)
|
||||
- 创建客户端
|
||||
|
||||
## 客户端
|
||||
- 下载客户端安装包并解压,进入到解压目录
|
||||
- 点击web管理中客户端前的+号,复制启动命令
|
||||
- 执行启动命令,linux直接执行即可,windows将./npc换成npc.exe用**cmd执行**
|
||||
|
||||
如果使用`powershell`运行,**请将ip括起来!**
|
||||
|
||||
如果需要注册到系统服务可查看[注册到系统服务](/use?id=注册到系统服务)
|
||||
|
||||
## 版本检查
|
||||
- 对客户端以及服务的均可以使用参数`-version`打印版本
|
||||
- `nps -version`或`./nps -version`
|
||||
- `npc -version`或`./npc -version`
|
||||
|
||||
## 配置
|
||||
- 客户端连接后,在web中配置对应穿透服务即可
|
||||
- 可以查看[使用示例](/example)
|
@ -1,24 +0,0 @@
|
||||
# 服务端配置文件
|
||||
- /etc/nps/conf/nps.conf
|
||||
|
||||
名称 | 含义
|
||||
---|---
|
||||
web_port | web管理端口
|
||||
web_password | web界面管理密码
|
||||
web_username | web界面管理账号
|
||||
web_base_url | web管理主路径,用于将web管理置于代理子路径后面
|
||||
bridge_port | 服务端客户端通信端口
|
||||
https_proxy_port | 域名代理https代理监听端口
|
||||
http_proxy_port | 域名代理http代理监听端口
|
||||
auth_key|web api密钥
|
||||
bridge_type|客户端与服务端连接方式kcp或tcp
|
||||
public_vkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式
|
||||
ip_limit|是否限制ip访问,true或false或忽略
|
||||
flow_store_interval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化
|
||||
log_level|日志输出级别
|
||||
auth_crypt_key | 获取服务端authKey时的aes加密密钥,16位
|
||||
p2p_ip| 服务端Ip,使用p2p模式必填
|
||||
p2p_port|p2p模式开启的udp端口
|
||||
pprof_ip|debug pprof 服务端ip
|
||||
pprof_port|debug pprof 端口
|
||||
disconnect_timeout|客户端连接超时,单位 5s,默认值 60,即 300s = 5mins
|
@ -1,5 +0,0 @@
|
||||
Thanks [jetbrains](https://www.jetbrains.com/?from=nps) for providing development tools for nps
|
||||
|
||||
<html>
|
||||
<img src="https://ftp.bmp.ovh/imgs/2019/12/6435398b0c7402b1.png" width="300" align=center />
|
||||
</html>
|
225
docs/use.md
@ -1,225 +0,0 @@
|
||||
# 基本使用
|
||||
## 无配置文件模式
|
||||
此模式的各种配置在服务端web管理中完成,客户端除运行一条命令外无需任何其他设置
|
||||
```
|
||||
./npc -server=ip:port -vkey=web界面中显示的密钥
|
||||
```
|
||||
## 注册到系统服务(开机启动、守护进程)
|
||||
对于linux、darwin
|
||||
- 注册:`sudo ./npc install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)`
|
||||
- 启动:`sudo npc start`
|
||||
- 停止:`sudo npc stop`
|
||||
- 如果需要更换命令内容需要先卸载`./npc uninstall`,再重新注册
|
||||
|
||||
对于windows,使用管理员身份运行cmd
|
||||
|
||||
- 注册:`npc.exe install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)`
|
||||
- 启动:`npc.exe start`
|
||||
- 停止:`npc.exe stop`
|
||||
- 如果需要更换命令内容需要先卸载`npc.exe uninstall`,再重新注册
|
||||
- 如果需要当客户端退出时自动重启客户端,请按照如图所示配置
|
||||

|
||||
|
||||
注册到服务后,日志文件windows位于当前目录下,linux和darwin位于/var/log/npc.log
|
||||
|
||||
## 客户端更新
|
||||
首先进入到对于的客户端二进制文件目录
|
||||
|
||||
请首先执行`sudo npc stop`或者`npc.exe stop`停止运行,然后
|
||||
|
||||
对于linux
|
||||
```shell
|
||||
sudo npc-update update
|
||||
```
|
||||
对于windows
|
||||
```shell
|
||||
npc-update.exe update
|
||||
```
|
||||
|
||||
更新完成后,执行执行`sudo npc start`或者`npc.exe start`重新运行即可完成升级
|
||||
|
||||
如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的npc二进制文件
|
||||
|
||||
## 配置文件模式
|
||||
此模式使用nps的公钥或者客户端私钥验证,各种配置在客户端完成,同时服务端web也可以进行管理
|
||||
```
|
||||
./npc -config=npc配置文件路径
|
||||
```
|
||||
## 配置文件说明
|
||||
[示例配置文件](https://github.com/ehang-io/nps/tree/master/conf/npc.conf)
|
||||
#### 全局配置
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
conn_type=tcp
|
||||
vkey=123
|
||||
username=111
|
||||
password=222
|
||||
compress=true
|
||||
crypt=true
|
||||
rate_limit=10000
|
||||
flow_limit=100
|
||||
remark=test
|
||||
max_conn=10
|
||||
#pprof_addr=0.0.0.0:9999
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
server_addr | 服务端ip/域名:port
|
||||
conn_type | 与服务端通信模式(tcp或kcp)
|
||||
vkey|服务端配置文件中的密钥(非web)
|
||||
username|socks5或http(s)密码保护用户名(可忽略)
|
||||
password|socks5或http(s)密码保护密码(可忽略)
|
||||
compress|是否压缩传输(true或false或忽略)
|
||||
crypt|是否加密传输(true或false或忽略)
|
||||
rate_limit|速度限制,可忽略
|
||||
flow_limit|流量限制,可忽略
|
||||
remark|客户端备注,可忽略
|
||||
max_conn|最大连接数,可忽略
|
||||
pprof_addr|debug pprof ip:port
|
||||
#### 域名代理
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[web1]
|
||||
host=a.proxy.com
|
||||
target_addr=127.0.0.1:8080,127.0.0.1:8082
|
||||
host_change=www.proxy.com
|
||||
header_set_proxy=nps
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
web1 | 备注
|
||||
host | 域名(http|https都可解析)
|
||||
target_addr|内网目标,负载均衡时多个目标,逗号隔开
|
||||
host_change|请求host修改
|
||||
header_xxx|请求header修改或添加,header_proxy表示添加header proxy:nps
|
||||
|
||||
#### tcp隧道模式
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[tcp]
|
||||
mode=tcp
|
||||
target_addr=127.0.0.1:8080
|
||||
server_port=9001
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | tcp
|
||||
server_port | 在服务端的代理端口
|
||||
tartget_addr|内网目标
|
||||
|
||||
#### udp隧道模式
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[udp]
|
||||
mode=udp
|
||||
target_addr=127.0.0.1:8080
|
||||
server_port=9002
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | udp
|
||||
server_port | 在服务端的代理端口
|
||||
target_addr|内网目标
|
||||
#### http代理模式
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[http]
|
||||
mode=httpProxy
|
||||
server_port=9003
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | httpProxy
|
||||
server_port | 在服务端的代理端口
|
||||
#### socks5代理模式
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[socks5]
|
||||
mode=socks5
|
||||
server_port=9004
|
||||
multi_account=multi_account.conf
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | socks5
|
||||
server_port | 在服务端的代理端口
|
||||
multi_account | socks5多账号配置文件(可选),配置后使用basic_username和basic_password无法通过认证
|
||||
#### 私密代理模式
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[secret_ssh]
|
||||
mode=secret
|
||||
password=ssh2
|
||||
target_addr=10.1.50.2:22
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | secret
|
||||
password | 唯一密钥
|
||||
target_addr|内网目标
|
||||
|
||||
#### p2p代理模式
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[p2p_ssh]
|
||||
mode=p2p
|
||||
password=ssh2
|
||||
target_addr=10.1.50.2:22
|
||||
```
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | p2p
|
||||
password | 唯一密钥
|
||||
target_addr|内网目标
|
||||
|
||||
|
||||
#### 文件访问模式
|
||||
利用nps提供一个公网可访问的本地文件服务,此模式仅客户端使用配置文件模式方可启动
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr=1.1.1.1:8024
|
||||
vkey=123
|
||||
[file]
|
||||
mode=file
|
||||
server_port=9100
|
||||
local_path=/tmp/
|
||||
strip_pre=/web/
|
||||
````
|
||||
|
||||
项 | 含义
|
||||
---|---
|
||||
mode | file
|
||||
server_port | 服务端开启的端口
|
||||
local_path|本地文件目录
|
||||
strip_pre|前缀
|
||||
|
||||
对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录
|
||||
|
||||
#### 断线重连
|
||||
```ini
|
||||
[common]
|
||||
auto_reconnection=true
|
||||
```
|
233
docs/webapi.md
@ -1,233 +0,0 @@
|
||||
获取客户端列表
|
||||
|
||||
```
|
||||
POST /client/list/
|
||||
```
|
||||
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| search | 搜索 |
|
||||
| order | 排序asc 正序 desc倒序 |
|
||||
| offset | 分页(第几页) |
|
||||
| limit | 条数(分页显示的条数) |
|
||||
|
||||
***
|
||||
获取单个客户端
|
||||
|
||||
```
|
||||
POST /client/getclient/
|
||||
```
|
||||
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 客户端id |
|
||||
|
||||
***
|
||||
添加客户端
|
||||
|
||||
```
|
||||
POST /client/add/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| remark | 备注 |
|
||||
| u | basic权限认证用户名 |
|
||||
| p | basic权限认证密码 |
|
||||
| limit | 条数(分页显示的条数) |
|
||||
| vkey | 客户端验证密钥 |
|
||||
| config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |
|
||||
| compress | 压缩1允许 0不允许 |
|
||||
| crypt | 是否加密(1或者0)1允许 0不允许 |
|
||||
| rate\_limit | 带宽限制 单位KB/S 空则为不限制 |
|
||||
| flow\_limit | 流量限制 单位M 空则为不限制 |
|
||||
| max\_conn | 客户端最大连接数量 空则为不限制 |
|
||||
| max\_tunnel | 客户端最大隧道数量 空则为不限制 |
|
||||
|
||||
***
|
||||
修改客户端
|
||||
|
||||
```
|
||||
POST /client/edit/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| remark | 备注 |
|
||||
| u | basic权限认证用户名 |
|
||||
| p | basic权限认证密码 |
|
||||
| limit | 条数(分页显示的条数) |
|
||||
| vkey | 客户端验证密钥 |
|
||||
| config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |
|
||||
| compress | 压缩1允许 0不允许 |
|
||||
| crypt | 是否加密(1或者0)1允许 0不允许 |
|
||||
| rate\_limit | 带宽限制 单位KB/S 空则为不限制 |
|
||||
| flow\_limit | 流量限制 单位M 空则为不限制 |
|
||||
| max\_conn | 客户端最大连接数量 空则为不限制 |
|
||||
| max\_tunnel | 客户端最大隧道数量 空则为不限制 |
|
||||
| id | 要修改的客户端id |
|
||||
|
||||
***
|
||||
删除客户端
|
||||
|
||||
```
|
||||
POST /client/del/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 要删除的客户端id |
|
||||
|
||||
***
|
||||
获取域名解析列表
|
||||
|
||||
```
|
||||
POST /index/hostlist/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| search | 搜索(可以搜域名/备注什么的) |
|
||||
| offset | 分页(第几页) |
|
||||
| limit | 条数(分页显示的条数) |
|
||||
|
||||
***
|
||||
添加域名解析
|
||||
|
||||
```
|
||||
POST /index/addhost/
|
||||
```
|
||||
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| remark | 备注 |
|
||||
| host | 域名 |
|
||||
| scheme | 协议类型(三种 all http https) |
|
||||
| location | url路由 空则为不限制 |
|
||||
| client\_id | 客户端id |
|
||||
| target | 内网目标(ip:端口) |
|
||||
| header | request header 请求头 |
|
||||
| hostchange | request host 请求主机 |
|
||||
|
||||
***
|
||||
修改域名解析
|
||||
|
||||
```
|
||||
POST /index/edithost/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| remark | 备注 |
|
||||
| host | 域名 |
|
||||
| scheme | 协议类型(三种 all http https) |
|
||||
| location | url路由 空则为不限制 |
|
||||
| client\_id | 客户端id |
|
||||
| target | 内网目标(ip:端口) |
|
||||
| header | request header 请求头 |
|
||||
| hostchange | request host 请求主机 |
|
||||
| id | 需要修改的域名解析id |
|
||||
|
||||
***
|
||||
删除域名解析
|
||||
|
||||
```
|
||||
POST /index/delhost/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 需要删除的域名解析id |
|
||||
|
||||
***
|
||||
获取单条隧道信息
|
||||
|
||||
```
|
||||
POST /index/getonetunnel/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 隧道的id |
|
||||
|
||||
***
|
||||
获取隧道列表
|
||||
|
||||
```
|
||||
POST /index/gettunnel/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| client\_id | 穿透隧道的客户端id |
|
||||
| type | 类型tcp udp httpProx socks5 secret p2p |
|
||||
| search | 搜索 |
|
||||
| offset | 分页(第几页) |
|
||||
| limit | 条数(分页显示的条数) |
|
||||
|
||||
***
|
||||
添加隧道
|
||||
|
||||
```
|
||||
POST /index/add/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| type | 类型tcp udp httpProx socks5 secret p2p |
|
||||
| remark | 备注 |
|
||||
| port | 服务端端口 |
|
||||
| target | 目标(ip:端口) |
|
||||
| client\_id | 客户端id |
|
||||
|
||||
***
|
||||
修改隧道
|
||||
|
||||
```
|
||||
POST /index/edit/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| type | 类型tcp udp httpProx socks5 secret p2p |
|
||||
| remark | 备注 |
|
||||
| port | 服务端端口 |
|
||||
| target | 目标(ip:端口) |
|
||||
| client\_id | 客户端id |
|
||||
| id | 隧道id |
|
||||
|
||||
***
|
||||
删除隧道
|
||||
|
||||
```
|
||||
POST /index/del/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 隧道id |
|
||||
|
||||
***
|
||||
隧道停止工作
|
||||
|
||||
```
|
||||
POST /index/stop/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 隧道id |
|
||||
|
||||
***
|
||||
隧道开始工作
|
||||
|
||||
```
|
||||
POST /index/start/
|
||||
```
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| id | 隧道id |
|
Before Width: | Height: | Size: 26 KiB |
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.nps.client"
|
||||
android:versionCode="1"
|
||||
android:versionName="0.26.10">
|
||||
|
||||
<application android:label="Npc" android:debuggable="true">
|
||||
<activity android:name="org.golang.app.GoNativeActivity"
|
||||
android:label="Npc"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<meta-data android:name="android.app.lib_name" android:value="npc"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
194
gui/npc/npc.go
@ -1,194 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"ehang.io/nps/client"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/daemon"
|
||||
"ehang.io/nps/lib/version"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath())
|
||||
logs.SetLogger("store")
|
||||
application := app.New()
|
||||
window := application.NewWindow("Npc " + version.VERSION)
|
||||
window.SetContent(WidgetScreen())
|
||||
window.Resize(fyne.NewSize(910, 350))
|
||||
|
||||
window.ShowAndRun()
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
start bool
|
||||
closing bool
|
||||
status = "Start!"
|
||||
connType = "tcp"
|
||||
cl = new(client.TRPClient)
|
||||
refreshCh = make(chan struct{})
|
||||
)
|
||||
|
||||
func WidgetScreen() fyne.CanvasObject {
|
||||
return fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, nil, nil),
|
||||
makeMainTab(),
|
||||
)
|
||||
}
|
||||
|
||||
func makeMainTab() *fyne.Container {
|
||||
serverPort := widget.NewEntry()
|
||||
serverPort.SetPlaceHolder("Server:Port")
|
||||
|
||||
vKey := widget.NewEntry()
|
||||
vKey.SetPlaceHolder("Vkey")
|
||||
radio := widget.NewRadioGroup([]string{"tcp", "kcp"}, func(s string) { connType = s })
|
||||
radio.Horizontal = true
|
||||
|
||||
button := widget.NewButton(status, func() {
|
||||
onclick(serverPort.Text, vKey.Text, connType)
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
<-refreshCh
|
||||
button.SetText(status)
|
||||
}
|
||||
}()
|
||||
|
||||
lo := widget.NewMultiLineEntry()
|
||||
lo.Disable()
|
||||
lo.Resize(fyne.NewSize(910, 250))
|
||||
slo := container.NewScroll(lo)
|
||||
slo.Resize(fyne.NewSize(910, 250))
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
lo.SetText(common.GetLogMsg())
|
||||
slo.Resize(fyne.NewSize(910, 250))
|
||||
}
|
||||
}()
|
||||
|
||||
sp, vk, ct := loadConfig()
|
||||
if sp != "" && vk != "" && ct != "" {
|
||||
serverPort.SetText(sp)
|
||||
vKey.SetText(vk)
|
||||
connType = ct
|
||||
radio.SetSelected(ct)
|
||||
onclick(sp, vk, ct)
|
||||
}
|
||||
|
||||
return container.NewVBox(
|
||||
widget.NewLabel("Npc "+version.VERSION),
|
||||
serverPort,
|
||||
vKey,
|
||||
radio,
|
||||
button,
|
||||
slo,
|
||||
)
|
||||
}
|
||||
|
||||
func onclick(s, v, c string) {
|
||||
start = !start
|
||||
if start {
|
||||
closing = false
|
||||
status = "Stop!"
|
||||
// init the npc
|
||||
fmt.Println("submit", s, v, c)
|
||||
sp, vk, ct := loadConfig()
|
||||
if sp != s || vk != v || ct != c {
|
||||
saveConfig(s, v, c)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
cl = client.NewRPClient(s, v, c, "", nil, 60)
|
||||
status = "Stop!"
|
||||
refreshCh <- struct{}{}
|
||||
cl.Start()
|
||||
logs.Warn("client closed, reconnecting in 5 seconds...")
|
||||
if closing {
|
||||
return
|
||||
}
|
||||
status = "Reconnecting..."
|
||||
refreshCh <- struct{}{}
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
// close the npc
|
||||
status = "Start!"
|
||||
closing = true
|
||||
if cl != nil {
|
||||
go cl.Close()
|
||||
cl = nil
|
||||
}
|
||||
}
|
||||
refreshCh <- struct{}{}
|
||||
}
|
||||
|
||||
func getDir() (dir string, err error) {
|
||||
if runtime.GOOS != "android" {
|
||||
dir, err = os.UserConfigDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
dir = "/data/data/org.nps.client/files"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func saveConfig(host, vkey, connType string) {
|
||||
data := strings.Join([]string{host, vkey, connType}, "\n")
|
||||
ph, err := getDir()
|
||||
if err != nil {
|
||||
logs.Warn("not found config dir")
|
||||
return
|
||||
}
|
||||
_ = os.Remove(path.Join(ph, "npc.conf"))
|
||||
f, err := os.OpenFile(path.Join(ph, "npc.conf"), os.O_CREATE|os.O_WRONLY, 0644)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
if _, err := f.Write([]byte(data)); err != nil {
|
||||
_ = f.Close() // ignore error; Write error takes precedence
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig() (host, vkey, connType string) {
|
||||
ph, err := getDir()
|
||||
if err != nil {
|
||||
logs.Warn("not found config dir")
|
||||
return
|
||||
}
|
||||
f, err := os.OpenFile(path.Join(ph, "npc.conf"), os.O_RDONLY, 0644)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
li := strings.Split(string(data), "\n")
|
||||
host = li[0]
|
||||
vkey = li[1]
|
||||
connType = li[2]
|
||||
return
|
||||
}
|
BIN
image/cpu1.png
Before Width: | Height: | Size: 276 KiB |
BIN
image/cpu2.png
Before Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 34 KiB |
BIN
image/http.png
Before Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 95 KiB |
BIN
image/qps.png
Before Width: | Height: | Size: 712 KiB |
BIN
image/sock5.png
Before Width: | Height: | Size: 97 KiB |
BIN
image/speed.png
Before Width: | Height: | Size: 690 KiB |
BIN
image/tcp.png
Before Width: | Height: | Size: 49 KiB |
BIN
image/udp.png
Before Width: | Height: | Size: 76 KiB |
BIN
image/web.png
Before Width: | Height: | Size: 290 KiB |
BIN
image/web2.png
Before Width: | Height: | Size: 232 KiB |
@ -1,821 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- 由 Microsoft Visio, SVG Export 生成 工作图.svg Page-1 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
|
||||
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="11.6929in" height="8.26772in"
|
||||
viewBox="0 0 841.89 595.276" xml:space="preserve" color-interpolation-filters="sRGB" class="st17">
|
||||
<v:documentProperties v:langID="2052" v:metric="true" v:viewMarkup="false">
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="msvNoAutoConnect" v:prompt="" v:val="VT0(0):26"/>
|
||||
</v:userDefs>
|
||||
</v:documentProperties>
|
||||
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.st1 {fill:#5b9bd5;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
|
||||
.st2 {stroke:#ffffff;stroke-linecap:butt;stroke-width:0.5}
|
||||
.st3 {fill:#1e4a73;font-family:Calibri;font-size:1.5em}
|
||||
.st4 {fill:#aad288;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
|
||||
.st5 {fill:#ffffff}
|
||||
.st6 {font-size:1em}
|
||||
.st7 {fill:#5b9bd5}
|
||||
.st8 {stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
|
||||
.st9 {fill:#a5a5a5}
|
||||
.st10 {marker-end:url(#mrkr4-135);marker-start:url(#mrkr4-133);stroke:#5592c9;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
|
||||
.st11 {fill:#5592c9;fill-opacity:1;stroke:#5592c9;stroke-opacity:1;stroke-width:0.37313432835821}
|
||||
.st12 {fill:#ffffff;stroke:none;stroke-linecap:butt;stroke-width:7.2}
|
||||
.st13 {fill:#41729d;font-family:Calibri;font-size:1.5em}
|
||||
.st14 {fill:none;stroke:#42829e;stroke-dasharray:45,27;stroke-linecap:round;stroke-linejoin:round;stroke-width:3}
|
||||
.st15 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
|
||||
.st16 {fill:#5b9bd5;font-family:Calibri;font-size:1.99999em}
|
||||
.st17 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]>
|
||||
</style>
|
||||
|
||||
<defs id="Markers">
|
||||
<g id="lend4">
|
||||
<path d="M 2 1 L 0 0 L 2 -1 L 2 1 " style="stroke:none"/>
|
||||
</g>
|
||||
<marker id="mrkr4-133" class="st11" v:arrowType="4" v:arrowSize="2" v:setback="5.12" refX="5.12" orient="auto"
|
||||
markerUnits="strokeWidth" overflow="visible">
|
||||
<use xlink:href="#lend4" transform="scale(2.68) "/>
|
||||
</marker>
|
||||
<marker id="mrkr4-135" class="st11" v:arrowType="4" v:arrowSize="2" v:setback="5.36" refX="-5.36" orient="auto"
|
||||
markerUnits="strokeWidth" overflow="visible">
|
||||
<use xlink:href="#lend4" transform="scale(-2.68,-2.68) "/>
|
||||
</marker>
|
||||
</defs>
|
||||
<g v:mID="0" v:index="1" v:groupContext="foregroundPage">
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
|
||||
</v:userDefs>
|
||||
<title>页-1</title>
|
||||
<v:pageProperties v:drawingScale="0.0393701" v:pageScale="0.0393701" v:drawingUnits="24" v:shadowOffsetX="8.50394"
|
||||
v:shadowOffsetY="-8.50394"/>
|
||||
<v:layer v:name="连接线" v:index="0"/>
|
||||
<g id="group1-1" transform="translate(418.11,-311.811)" v:mID="1" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(防火墙)"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>防火墙</title>
|
||||
<desc>NAT</desc>
|
||||
<g id="shape2-2" v:mID="2" v:groupContext="shape" transform="translate(0.545123,-7.63784)">
|
||||
<title>工作表.2</title>
|
||||
<rect x="0" y="539.685" width="69.7759" height="55.5905" class="st1"/>
|
||||
</g>
|
||||
<g id="shape3-4" v:mID="3" v:groupContext="shape" transform="translate(0.795123,-7.88784)">
|
||||
<title>工作表.3</title>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
|
||||
</v:userDefs>
|
||||
<path d="M0 587.41 L69.28 587.41" class="st2"/>
|
||||
<path d="M0 579.54 L69.28 579.54" class="st2"/>
|
||||
<path d="M0 571.67 L69.28 571.67" class="st2"/>
|
||||
<path d="M0 563.8 L69.28 563.8" class="st2"/>
|
||||
<path d="M0 555.93 L69.28 555.93" class="st2"/>
|
||||
<path d="M0 548.06 L69.28 548.06" class="st2"/>
|
||||
<path d="M17.32 595.28 L17.32 587.41" class="st2"/>
|
||||
<path d="M34.64 595.28 L34.64 587.41" class="st2"/>
|
||||
<path d="M51.96 595.28 L51.96 587.41" class="st2"/>
|
||||
<path d="M8.66 587.41 L8.66 579.54" class="st2"/>
|
||||
<path d="M25.98 587.41 L25.98 579.54" class="st2"/>
|
||||
<path d="M43.3 587.41 L43.3 579.54" class="st2"/>
|
||||
<path d="M60.62 587.41 L60.62 579.54" class="st2"/>
|
||||
<path d="M17.32 579.54 L17.32 571.67" class="st2"/>
|
||||
<path d="M34.64 579.54 L34.64 571.67" class="st2"/>
|
||||
<path d="M51.96 579.54 L51.96 571.67" class="st2"/>
|
||||
<path d="M8.66 571.67 L8.66 563.8" class="st2"/>
|
||||
<path d="M25.98 571.67 L25.98 563.8" class="st2"/>
|
||||
<path d="M43.3 571.67 L43.3 563.8" class="st2"/>
|
||||
<path d="M60.62 571.67 L60.62 563.8" class="st2"/>
|
||||
<path d="M17.32 563.8 L17.32 555.93" class="st2"/>
|
||||
<path d="M34.64 563.8 L34.64 555.93" class="st2"/>
|
||||
<path d="M51.96 563.8 L51.96 555.93" class="st2"/>
|
||||
<path d="M8.66 555.93 L8.66 548.06" class="st2"/>
|
||||
<path d="M25.98 555.93 L25.98 548.06" class="st2"/>
|
||||
<path d="M43.3 555.93 L43.3 548.06" class="st2"/>
|
||||
<path d="M60.62 555.93 L60.62 548.06" class="st2"/>
|
||||
<path d="M17.32 548.06 L17.32 540.19" class="st2"/>
|
||||
<path d="M34.64 548.06 L34.64 540.19" class="st2"/>
|
||||
<path d="M51.96 548.06 L51.96 540.19" class="st2"/>
|
||||
</g>
|
||||
<g id="shape1-36" v:mID="1" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="610.077" width="42.88" height="29.6036"/>
|
||||
<text x="20.03" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NAT</text> </g>
|
||||
</g>
|
||||
<g id="group4-38" transform="translate(701.575,-311.811)" v:mID="4" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Memory" v:lbl="内存" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(服务器)"/>
|
||||
<v:cp v:nameU="BelongsTo" v:lbl="属于" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>服务器</title>
|
||||
<desc>Application2 10.0.0.4:PORT</desc>
|
||||
<g id="shape5-39" v:mID="5" v:groupContext="shape" transform="translate(12.8133,0)">
|
||||
<title>工作表.5</title>
|
||||
<rect x="0" y="524.409" width="45.2395" height="70.8661" class="st1"/>
|
||||
</g>
|
||||
<g id="shape6-41" v:mID="6" v:groupContext="shape" transform="translate(46.625,-30.2513)">
|
||||
<title>工作表.6</title>
|
||||
<ellipse cx="2.73472" cy="592.541" rx="2.73472" ry="2.73472" class="st4"/>
|
||||
</g>
|
||||
<g id="shape7-43" v:mID="7" v:groupContext="shape" transform="translate(30.0295,-11.6164)">
|
||||
<title>工作表.7</title>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
|
||||
</v:userDefs>
|
||||
<path d="M-0 595.28 L22.06 595.28 L22.06 593.5 L-0 593.5 L-0 595.28 ZM-0 589.9 L22.06 589.9 L22.06 588.13 L-0 588.13
|
||||
L-0 589.9 ZM-0 584.53 L22.06 584.53 L22.06 582.76 L-0 582.76 L-0 584.53 Z" class="st5"/>
|
||||
</g>
|
||||
<g id="shape4-46" v:mID="4" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="620.877" width="115.9" height="51.2036"/>
|
||||
<text x="-10.6" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Application2<v:newlineChar/><tspan
|
||||
x="-16.48" dy="1.2em" class="st6">10.0.0.4:PORT</tspan></text> </g>
|
||||
</g>
|
||||
<g id="group8-49" transform="translate(701.575,-496.063)" v:mID="8" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Memory" v:lbl="内存" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(服务器)"/>
|
||||
<v:cp v:nameU="BelongsTo" v:lbl="属于" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>服务器.8</title>
|
||||
<desc>Application1 10.0.0.3:PORT</desc>
|
||||
<g id="shape9-50" v:mID="9" v:groupContext="shape" transform="translate(12.8133,0)">
|
||||
<title>工作表.9</title>
|
||||
<rect x="0" y="524.409" width="45.2395" height="70.8661" class="st1"/>
|
||||
</g>
|
||||
<g id="shape10-52" v:mID="10" v:groupContext="shape" transform="translate(46.625,-30.2513)">
|
||||
<title>工作表.10</title>
|
||||
<ellipse cx="2.73472" cy="592.541" rx="2.73472" ry="2.73472" class="st4"/>
|
||||
</g>
|
||||
<g id="shape11-54" v:mID="11" v:groupContext="shape" transform="translate(30.0295,-11.6164)">
|
||||
<title>工作表.11</title>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
|
||||
</v:userDefs>
|
||||
<path d="M-0 595.28 L22.06 595.28 L22.06 593.5 L-0 593.5 L-0 595.28 ZM-0 589.9 L22.06 589.9 L22.06 588.13 L-0 588.13
|
||||
L-0 589.9 ZM-0 584.53 L22.06 584.53 L22.06 582.76 L-0 582.76 L-0 584.53 Z" class="st5"/>
|
||||
</g>
|
||||
<g id="shape8-57" v:mID="8" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="620.877" width="115.9" height="51.2036"/>
|
||||
<text x="-10.6" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Application1<v:newlineChar/><tspan
|
||||
x="-16.48" dy="1.2em" class="st6">10.0.0.3:PORT</tspan></text> </g>
|
||||
</g>
|
||||
<g id="group12-60" transform="translate(701.575,-127.559)" v:mID="12" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Memory" v:lbl="内存" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(服务器)"/>
|
||||
<v:cp v:nameU="BelongsTo" v:lbl="属于" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>服务器.12</title>
|
||||
<desc>Application3 10.0.0.5:PORT</desc>
|
||||
<g id="shape13-61" v:mID="13" v:groupContext="shape" transform="translate(12.8133,0)">
|
||||
<title>工作表.13</title>
|
||||
<rect x="0" y="524.409" width="45.2395" height="70.8661" class="st1"/>
|
||||
</g>
|
||||
<g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(46.625,-30.2513)">
|
||||
<title>工作表.14</title>
|
||||
<ellipse cx="2.73472" cy="592.541" rx="2.73472" ry="2.73472" class="st4"/>
|
||||
</g>
|
||||
<g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(30.0295,-11.6164)">
|
||||
<title>工作表.15</title>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
|
||||
</v:userDefs>
|
||||
<path d="M-0 595.28 L22.06 595.28 L22.06 593.5 L-0 593.5 L-0 595.28 ZM-0 589.9 L22.06 589.9 L22.06 588.13 L-0 588.13
|
||||
L-0 589.9 ZM-0 584.53 L22.06 584.53 L22.06 582.76 L-0 582.76 L-0 584.53 Z" class="st5"/>
|
||||
</g>
|
||||
<g id="shape12-68" v:mID="12" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="620.877" width="115.9" height="51.2036"/>
|
||||
<text x="-10.6" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Application3<v:newlineChar/><tspan
|
||||
x="-16.48" dy="1.2em" class="st6">10.0.0.5:PORT</tspan></text> </g>
|
||||
</g>
|
||||
<g id="group16-71" transform="translate(538.583,-311.811)" v:mID="16" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="建筑物" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(计算机)"/>
|
||||
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(大型机)"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>主机</title>
|
||||
<desc>NPC Client 10.0.0.2 Dial To: ->10.0.0.3:PORT ->10.0.0.4:PORT ...</desc>
|
||||
<g id="shape17-72" v:mID="17" v:groupContext="shape" transform="translate(5.68158,0)">
|
||||
<title>工作表.17</title>
|
||||
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28 Z" class="st7"/>
|
||||
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28" class="st8"/>
|
||||
<path d="M29.75 595.28 L29.75 524.41" class="st8"/>
|
||||
</g>
|
||||
<g id="shape18-76" v:mID="18" v:groupContext="shape" transform="translate(11.5726,-5.14536)">
|
||||
<title>工作表.18</title>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
|
||||
</v:userDefs>
|
||||
<path d="M36.09 551.42 L49.23 551.42 L49.23 536.38 L36.09 536.38 L36.09 551.42 ZM14.89 542.47 L16.68 542.47 L16.68
|
||||
536.06 L14.89 536.06 L14.89 542.47 ZM14.89 551.71 L16.68 551.71 L16.68 545.3 L14.89 545.3 L14.89 551.71
|
||||
ZM10.63 542.47 L12.43 542.47 L12.43 536.06 L10.63 536.06 L10.63 542.47 ZM10.63 551.71 L12.43 551.71
|
||||
L12.43 545.3 L10.63 545.3 L10.63 551.71 ZM4.25 542.47 L6.05 542.47 L6.05 536.06 L4.25 536.06 L4.25 542.47
|
||||
ZM4.25 551.71 L6.05 551.71 L6.05 545.3 L4.25 545.3 L4.25 551.71 ZM0 542.47 L1.79 542.47 L1.79 536.06
|
||||
L0 536.06 L0 542.47 ZM0 551.71 L1.79 551.71 L1.79 545.3 L0 545.3 L0 551.71 ZM33.82 586.45 L33.82 587.7
|
||||
L49.39 587.69 L49.39 586.44 L33.82 586.45 ZM33.82 590.24 L33.82 591.49 L49.39 591.48 L49.39 590.24 L33.82
|
||||
590.24 ZM33.82 594.03 L33.82 595.28 L49.39 595.27 L49.39 594.03 L33.82 594.03 ZM2.96 587.7 L18.52 587.7
|
||||
L18.52 586.45 L2.95 586.45 L2.96 587.7 ZM2.96 591.49 L18.52 591.49 L18.52 590.24 L2.95 590.24 L2.96
|
||||
591.49 ZM2.96 595.28 L18.52 595.28 L18.52 594.03 L2.95 594.03 L2.96 595.28 Z" class="st5"/>
|
||||
</g>
|
||||
<g id="shape19-79" v:mID="19" v:groupContext="shape" transform="translate(49.0815,-50.4059)">
|
||||
<title>工作表.19</title>
|
||||
<path d="M8.15 583.79 A1.11073 1.11073 -180 1 0 10.24 584.56 A1.11073 1.11073 -180 1 0 8.15 583.79 ZM8.17 587.08
|
||||
A1.11073 1.11073 -180 1 0 10.22 587.93 A1.11073 1.11073 -180 1 0 8.17 587.08 ZM8.18 590.39 A1.11073
|
||||
1.11073 -180 1 0 10.21 591.28 A1.11073 1.11073 -180 1 0 8.18 590.39 ZM8.18 593.71 A1.11073 1.11073 -180
|
||||
1 0 10.21 594.6 A1.11073 1.11073 -180 1 0 8.18 593.71 ZM4.1 583.84 A1.11073 1.11073 -180 1 0 6.21 584.51
|
||||
A1.11073 1.11073 -180 1 0 4.1 583.84 ZM4.11 587.14 A1.11073 1.11073 -180 1 0 6.2 587.87 A1.11073 1.11073
|
||||
-180 1 0 4.11 587.14 ZM4.11 590.44 A1.11073 1.11073 -180 1 0 6.19 591.22 A1.11073 1.11073 -180 1 0 4.11
|
||||
590.44 ZM4.11 593.77 A1.11073 1.11073 -180 1 0 6.19 594.55 A1.11073 1.11073 -180 1 0 4.11 593.77 ZM0.04
|
||||
583.9 A1.11144 1.11144 -180 1 0 2.19 584.46 A1.11144 1.11144 -180 1 0 0.04 583.9 ZM0.05 587.19 A1.11144
|
||||
1.11144 -180 1 0 2.18 587.82 A1.11144 1.11144 -180 1 0 0.05 587.19 ZM0.05 590.5 A1.11144 1.11144 -180
|
||||
1 0 2.17 591.16 A1.11144 1.11144 -180 1 0 0.05 590.5 ZM0.05 593.83 A1.11144 1.11144 -180 1 0 2.17 594.49
|
||||
A1.11144 1.11144 -180 1 0 0.05 593.83 Z" class="st9"/>
|
||||
</g>
|
||||
<g id="shape16-82" v:mID="16" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="674.877" width="130.38" height="159.204"/>
|
||||
<text x="20.18" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPC<v:newlineChar/><tspan
|
||||
x="14.28" dy="1.2em" class="st6">Client<v:newlineChar/></tspan><tspan x="5.81" dy="1.2em" class="st6">10.0.0.2<v:newlineChar/></tspan><tspan
|
||||
x="7.88" dy="1.2em" class="st6">Dial To:<v:newlineChar/></tspan><tspan x="-23.72" dy="1.2em"
|
||||
class="st6">-</tspan>>10.0.0.3:PORT<v:newlineChar/><tspan x="-23.72" dy="1.2em" class="st6">-</tspan>>10.0.0.4:PORT<v:newlineChar/><tspan
|
||||
x="-23.72" dy="1.2em" class="st6">-</tspan>>10.0.0.5:PORT</text> </g>
|
||||
</g>
|
||||
<g id="group20-90" transform="translate(212.598,-311.811)" v:mID="20" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="建筑物" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
|
||||
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
|
||||
v:ask="false" v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(计算机)"/>
|
||||
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(大型机)"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>主机.20</title>
|
||||
<desc>NPS Server 1.1.1.1 Listen On: 8003->10.0.0.3:PORT 8004->10.0....</desc>
|
||||
<g id="shape21-91" v:mID="21" v:groupContext="shape" transform="translate(5.68158,0)">
|
||||
<title>工作表.21</title>
|
||||
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28 Z" class="st7"/>
|
||||
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28" class="st8"/>
|
||||
<path d="M29.75 595.28 L29.75 524.41" class="st8"/>
|
||||
</g>
|
||||
<g id="shape22-95" v:mID="22" v:groupContext="shape" transform="translate(11.5726,-5.14536)">
|
||||
<title>工作表.22</title>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
|
||||
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
|
||||
</v:userDefs>
|
||||
<path d="M36.09 551.42 L49.23 551.42 L49.23 536.38 L36.09 536.38 L36.09 551.42 ZM14.89 542.47 L16.68 542.47 L16.68
|
||||
536.06 L14.89 536.06 L14.89 542.47 ZM14.89 551.71 L16.68 551.71 L16.68 545.3 L14.89 545.3 L14.89 551.71
|
||||
ZM10.63 542.47 L12.43 542.47 L12.43 536.06 L10.63 536.06 L10.63 542.47 ZM10.63 551.71 L12.43 551.71
|
||||
L12.43 545.3 L10.63 545.3 L10.63 551.71 ZM4.25 542.47 L6.05 542.47 L6.05 536.06 L4.25 536.06 L4.25 542.47
|
||||
ZM4.25 551.71 L6.05 551.71 L6.05 545.3 L4.25 545.3 L4.25 551.71 ZM0 542.47 L1.79 542.47 L1.79 536.06
|
||||
L0 536.06 L0 542.47 ZM0 551.71 L1.79 551.71 L1.79 545.3 L0 545.3 L0 551.71 ZM33.82 586.45 L33.82 587.7
|
||||
L49.39 587.69 L49.39 586.44 L33.82 586.45 ZM33.82 590.24 L33.82 591.49 L49.39 591.48 L49.39 590.24 L33.82
|
||||
590.24 ZM33.82 594.03 L33.82 595.28 L49.39 595.27 L49.39 594.03 L33.82 594.03 ZM2.96 587.7 L18.52 587.7
|
||||
L18.52 586.45 L2.95 586.45 L2.96 587.7 ZM2.96 591.49 L18.52 591.49 L18.52 590.24 L2.95 590.24 L2.96
|
||||
591.49 ZM2.96 595.28 L18.52 595.28 L18.52 594.03 L2.95 594.03 L2.96 595.28 Z" class="st5"/>
|
||||
</g>
|
||||
<g id="shape23-98" v:mID="23" v:groupContext="shape" transform="translate(49.0815,-50.4059)">
|
||||
<title>工作表.23</title>
|
||||
<path d="M8.15 583.79 A1.11073 1.11073 -180 1 0 10.24 584.56 A1.11073 1.11073 -180 1 0 8.15 583.79 ZM8.17 587.08
|
||||
A1.11073 1.11073 -180 1 0 10.22 587.93 A1.11073 1.11073 -180 1 0 8.17 587.08 ZM8.18 590.39 A1.11073
|
||||
1.11073 -180 1 0 10.21 591.28 A1.11073 1.11073 -180 1 0 8.18 590.39 ZM8.18 593.71 A1.11073 1.11073 -180
|
||||
1 0 10.21 594.6 A1.11073 1.11073 -180 1 0 8.18 593.71 ZM4.1 583.84 A1.11073 1.11073 -180 1 0 6.21 584.51
|
||||
A1.11073 1.11073 -180 1 0 4.1 583.84 ZM4.11 587.14 A1.11073 1.11073 -180 1 0 6.2 587.87 A1.11073 1.11073
|
||||
-180 1 0 4.11 587.14 ZM4.11 590.44 A1.11073 1.11073 -180 1 0 6.19 591.22 A1.11073 1.11073 -180 1 0 4.11
|
||||
590.44 ZM4.11 593.77 A1.11073 1.11073 -180 1 0 6.19 594.55 A1.11073 1.11073 -180 1 0 4.11 593.77 ZM0.04
|
||||
583.9 A1.11144 1.11144 -180 1 0 2.19 584.46 A1.11144 1.11144 -180 1 0 0.04 583.9 ZM0.05 587.19 A1.11144
|
||||
1.11144 -180 1 0 2.18 587.82 A1.11144 1.11144 -180 1 0 0.05 587.19 ZM0.05 590.5 A1.11144 1.11144 -180
|
||||
1 0 2.17 591.16 A1.11144 1.11144 -180 1 0 0.05 590.5 ZM0.05 593.83 A1.11144 1.11144 -180 1 0 2.17 594.49
|
||||
A1.11144 1.11144 -180 1 0 0.05 593.83 Z" class="st9"/>
|
||||
</g>
|
||||
<g id="shape20-101" v:mID="20" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="674.877" width="166.87" height="159.204"/>
|
||||
<text x="20.84" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPS<v:newlineChar/><tspan
|
||||
x="12" dy="1.2em" class="st6">Server<v:newlineChar/></tspan><tspan x="10.37" dy="1.2em" class="st6">1.1.1.1<v:newlineChar/></tspan><tspan
|
||||
x="-1.29" dy="1.2em" class="st6">Listen On:<v:newlineChar/></tspan><tspan x="-41.96" dy="1.2em"
|
||||
class="st6">8003</tspan>->10.0.0.3:PORT<v:newlineChar/><tspan x="-41.96" dy="1.2em" class="st6">8004</tspan>->10.0.0.4:PORT<v:newlineChar/><tspan
|
||||
x="-41.96" dy="1.2em" class="st6">8005</tspan>->10.0.0.5:PORT</text> </g>
|
||||
</g>
|
||||
<g id="group24-109" transform="translate(49.6063,-496.063)" v:mID="24" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="Name" v:lbl="名称" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(连接性)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(用户)"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>用户</title>
|
||||
<desc>User1 Wants:APP1</desc>
|
||||
<g id="shape25-110" v:mID="25" v:groupContext="shape" transform="translate(18.0575,0)">
|
||||
<title>工作表.25</title>
|
||||
<path d="M26.29 533.22 A8.81 8.81 -180 1 0 8.67 533.22 A8.81 8.81 -180 1 0 26.29 533.22 ZM27.58 544.41 L7.17 544.41
|
||||
C3.22 544.41 0 547.62 0 551.58 L0 576.58 L5.59 576.58 L5.59 562.03 L7.45 562.03 L7.45 595.28 L16.55
|
||||
595.28 L16.55 580.42 C16.55 579.9 16.97 579.48 17.48 579.48 C18 579.48 18.42 579.9 18.42 580.42 L18.42
|
||||
595.28 L28.04 595.28 L28.04 561.98 L29.91 561.98 L29.91 576.58 L34.75 576.58 L34.75 551.58 C34.75 547.62
|
||||
31.54 544.41 27.58 544.41 Z" class="st1"/>
|
||||
</g>
|
||||
<g id="shape24-112" v:mID="24" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="620.877" width="102.19" height="51.2036"/>
|
||||
<text x="13.96" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>User1<v:newlineChar/><tspan
|
||||
x="-9.62" dy="1.2em" class="st6">Wants:APP1</tspan></text> </g>
|
||||
</g>
|
||||
<g id="group26-115" transform="translate(49.6063,-311.811)" v:mID="26" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="Name" v:lbl="名称" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(连接性)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(用户)"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>用户.26</title>
|
||||
<desc>User2 Wants:APP2</desc>
|
||||
<g id="shape27-116" v:mID="27" v:groupContext="shape" transform="translate(18.0575,0)">
|
||||
<title>工作表.27</title>
|
||||
<path d="M26.29 533.22 A8.81 8.81 -180 1 0 8.67 533.22 A8.81 8.81 -180 1 0 26.29 533.22 ZM27.58 544.41 L7.17 544.41
|
||||
C3.22 544.41 0 547.62 0 551.58 L0 576.58 L5.59 576.58 L5.59 562.03 L7.45 562.03 L7.45 595.28 L16.55
|
||||
595.28 L16.55 580.42 C16.55 579.9 16.97 579.48 17.48 579.48 C18 579.48 18.42 579.9 18.42 580.42 L18.42
|
||||
595.28 L28.04 595.28 L28.04 561.98 L29.91 561.98 L29.91 576.58 L34.75 576.58 L34.75 551.58 C34.75 547.62
|
||||
31.54 544.41 27.58 544.41 Z" class="st1"/>
|
||||
</g>
|
||||
<g id="shape26-118" v:mID="26" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="620.877" width="102.19" height="51.2036"/>
|
||||
<text x="13.96" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>User2<v:newlineChar/><tspan
|
||||
x="-9.62" dy="1.2em" class="st6">Wants:APP2</tspan></text> </g>
|
||||
</g>
|
||||
<g id="group28-121" transform="translate(49.6063,-127.559)" v:mID="28" v:groupContext="group">
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="Name" v:lbl="名称" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
|
||||
v:langID="2052" v:cal="0"/>
|
||||
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(连接性)"/>
|
||||
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
|
||||
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(用户)"/>
|
||||
</v:custProps>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
|
||||
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
|
||||
</v:userDefs>
|
||||
<title>用户.28</title>
|
||||
<desc>User3 Wants:APP3</desc>
|
||||
<g id="shape29-122" v:mID="29" v:groupContext="shape" transform="translate(18.0575,0)">
|
||||
<title>工作表.29</title>
|
||||
<path d="M26.29 533.22 A8.81 8.81 -180 1 0 8.67 533.22 A8.81 8.81 -180 1 0 26.29 533.22 ZM27.58 544.41 L7.17 544.41
|
||||
C3.22 544.41 0 547.62 0 551.58 L0 576.58 L5.59 576.58 L5.59 562.03 L7.45 562.03 L7.45 595.28 L16.55
|
||||
595.28 L16.55 580.42 C16.55 579.9 16.97 579.48 17.48 579.48 C18 579.48 18.42 579.9 18.42 580.42 L18.42
|
||||
595.28 L28.04 595.28 L28.04 561.98 L29.91 561.98 L29.91 576.58 L34.75 576.58 L34.75 551.58 C34.75 547.62
|
||||
31.54 544.41 27.58 544.41 Z" class="st1"/>
|
||||
</g>
|
||||
<g id="shape28-124" v:mID="28" v:groupContext="groupContent">
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="35.4331" cy="620.877" width="102.19" height="51.2036"/>
|
||||
<text x="13.96" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>User3<v:newlineChar/><tspan
|
||||
x="-9.62" dy="1.2em" class="st6">Wants:APP3</tspan></text> </g>
|
||||
</g>
|
||||
<g id="shape1003-127" v:mID="1003" v:groupContext="shape" v:layerMember="0" transform="translate(99.8466,-514.757)">
|
||||
<title>动态连接线.1003</title>
|
||||
<desc>->8003 Multi Conn</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="56.059" cy="684.836" width="93.29" height="51.2036"/>
|
||||
<path d="M5.09 601.03 L5.33 601.3 L113.11 723.13" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="15.4535" y="663.236" width="81.2109" height="43.1999" class="st12"/>
|
||||
<text x="30.58" y="679.44" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>->8003<v:newlineChar/><tspan
|
||||
x="15.45" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
|
||||
<g id="shape1004-139" v:mID="1004" v:groupContext="shape" v:layerMember="0" transform="translate(102.415,-340.157)">
|
||||
<title>动态连接线.1004</title>
|
||||
<desc>->8004 Multi Conn</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="57.9325" cy="588.189" width="93.29" height="51.2036"/>
|
||||
<path d="M7.68 588.19 L8.04 588.19 L107.83 588.19" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="17.327" y="566.589" width="81.2109" height="43.1999" class="st12"/>
|
||||
<text x="32.45" y="582.79" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>->8004<v:newlineChar/><tspan
|
||||
x="17.33" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
|
||||
<g id="shape1005-149" v:mID="1005" v:groupContext="shape" v:layerMember="0" transform="translate(98.1493,-177.812)">
|
||||
<title>动态连接线.1005</title>
|
||||
<desc>->8005 Multi Conn</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="60.0654" cy="527.376" width="93.29" height="51.2036"/>
|
||||
<path d="M5.09 589.52 L5.33 589.25 L114.8 465.5" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="19.4599" y="505.776" width="81.2109" height="43.1999" class="st12"/>
|
||||
<text x="34.58" y="521.98" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>->8005<v:newlineChar/><tspan
|
||||
x="19.46" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
|
||||
<g id="shape1006-159" v:mID="1006" v:groupContext="shape" v:layerMember="0" transform="translate(277.783,-354.331)">
|
||||
<title>动态连接线.1006</title>
|
||||
<desc>NPS & NPC Multiplexing Connection TCP or KCP Only One Conn Pe...</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="70.4362" cy="602.362" width="103.62" height="159.204"/>
|
||||
<path d="M7.68 602.36 L8.04 602.36 L132.83 602.36" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="24.6671" y="526.762" width="91.5381" height="151.2" class="st12"/>
|
||||
<text x="30.38" y="542.96" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPS & NPC<v:newlineChar/><tspan
|
||||
x="24.67" dy="1.2em" class="st6">Multiplexing<v:newlineChar/></tspan><tspan x="28.6" dy="1.2em" class="st6">Connection<v:newlineChar/></tspan><tspan
|
||||
x="30.53" dy="1.2em" class="st6">TCP or KCP<v:newlineChar/></tspan><tspan x="36.41" dy="1.2em" class="st6">Only One<v:newlineChar/></tspan><tspan
|
||||
x="32.66" dy="1.2em" class="st6">Conn Peer<v:newlineChar/></tspan><tspan x="55.18" dy="1.2em" class="st6">NPC</tspan></text> </g>
|
||||
<g id="shape1007-174" v:mID="1007" v:groupContext="shape" v:layerMember="0" transform="translate(603.767,-380.876)">
|
||||
<title>动态连接线.1007</title>
|
||||
<desc>->PORT Multi Conn</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="55.0044" cy="550.955" width="93.29" height="51.2036"/>
|
||||
<path d="M5.09 589.52 L5.33 589.25 L105.29 476.25" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="14.3989" y="529.355" width="81.2109" height="43.1999" class="st12"/>
|
||||
<text x="27.89" y="545.56" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>->PORT<v:newlineChar/><tspan
|
||||
x="14.4" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
|
||||
<g id="shape1008-184" v:mID="1008" v:groupContext="shape" v:layerMember="0" transform="translate(603.767,-340.157)">
|
||||
<title>动态连接线.1008</title>
|
||||
<desc>->PORT Multi Conn</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="55.3104" cy="588.189" width="93.29" height="51.2036"/>
|
||||
<path d="M7.68 588.19 L8.04 588.19 L102.58 588.19" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="14.7049" y="566.589" width="81.2109" height="43.1999" class="st12"/>
|
||||
<text x="28.19" y="582.79" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>->PORT<v:newlineChar/><tspan
|
||||
x="14.7" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
|
||||
<g id="shape1009-194" v:mID="1009" v:groupContext="shape" v:layerMember="0" transform="translate(603.767,-313.612)">
|
||||
<title>动态连接线.1009</title>
|
||||
<desc>->PORT Multi Conn</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="68.0438" cy="667.943" width="93.29" height="51.2036"/>
|
||||
<path d="M5.09 601.03 L5.33 601.3 L105.29 714.3" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="27.4383" y="646.343" width="81.2109" height="43.1999" class="st12"/>
|
||||
<text x="40.93" y="662.54" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>->PORT<v:newlineChar/><tspan
|
||||
x="27.44" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
|
||||
<g id="shape1010-204" v:mID="1010" v:groupContext="shape" v:layerMember="0" transform="translate(488.431,-340.157)">
|
||||
<title>动态连接线.1010</title>
|
||||
<desc>NPS & NPC</desc>
|
||||
<v:userDefs>
|
||||
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||
</v:userDefs>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="27.9165" cy="588.189" width="90" height="72.8036"/>
|
||||
<path d="M7.68 588.19 L8.04 588.19 L47.79 588.19" class="st10"/>
|
||||
<rect v:rectContext="textBkgnd" x="12.6587" y="555.789" width="30.5156" height="64.7998" class="st12"/>
|
||||
<text x="13.32" y="571.99" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPS<v:newlineChar/><tspan
|
||||
x="21.78" dy="1.2em" class="st6">&<v:newlineChar/></tspan><tspan x="12.66" dy="1.2em" class="st6">NPC</tspan></text> </g>
|
||||
<g id="shape1011-215" v:mID="1011" v:groupContext="shape" transform="translate(34.0157,-62.8844)">
|
||||
<title>工作表.1011</title>
|
||||
<path d="M0 595.28 L398.27 595.28 L398.27 85.04 L0 85.04 L0 595.28 Z" class="st14"/>
|
||||
</g>
|
||||
<g id="shape1012-217" v:mID="1012" v:groupContext="shape" transform="translate(473.386,-62.8844)">
|
||||
<title>工作表.1012</title>
|
||||
<path d="M0 595.28 L320.31 595.28 L320.31 85.04 L0 85.04 L0 595.28 Z" class="st14"/>
|
||||
</g>
|
||||
<g id="shape1013-219" v:mID="1013" v:groupContext="shape" transform="translate(255.118,-496.063)">
|
||||
<title>工作表.1013</title>
|
||||
<desc>Internet</desc>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="63.7795" cy="559.843" width="127.56" height="70.8661"/>
|
||||
<rect x="0" y="524.409" width="127.559" height="70.8661" class="st15"/>
|
||||
<text x="23.98" y="567.04" class="st16" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Internet</text> </g>
|
||||
<g id="shape1014-222" v:mID="1014" v:groupContext="shape" transform="translate(517.323,-515.866)">
|
||||
<title>工作表.1014</title>
|
||||
<desc>Intranet</desc>
|
||||
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
|
||||
<v:textRect cx="76" cy="579.646" width="152.01" height="31.2598"/>
|
||||
<rect x="0" y="564.016" width="152" height="31.2598" class="st15"/>
|
||||
<text x="36.43" y="586.85" class="st16" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Intranet</text> </g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 58 KiB |
102
lib/cache/lru.go
vendored
@ -1,102 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Cache is an LRU cache. It is safe for concurrent access.
|
||||
type Cache struct {
|
||||
// MaxEntries is the maximum number of cache entries before
|
||||
// an item is evicted. Zero means no limit.
|
||||
MaxEntries int
|
||||
|
||||
//Execute this callback function when an element is culled
|
||||
OnEvicted func(key Key, value interface{})
|
||||
|
||||
ll *list.List //list
|
||||
cache sync.Map
|
||||
}
|
||||
|
||||
// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
|
||||
type Key interface{}
|
||||
|
||||
type entry struct {
|
||||
key Key
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// New creates a new Cache.
|
||||
// If maxEntries is 0, the cache has no length limit.
|
||||
// that eviction is done by the caller.
|
||||
func New(maxEntries int) *Cache {
|
||||
return &Cache{
|
||||
MaxEntries: maxEntries,
|
||||
ll: list.New(),
|
||||
//cache: make(map[interface{}]*list.Element),
|
||||
}
|
||||
}
|
||||
|
||||
// If the key value already exists, move the key to the front
|
||||
func (c *Cache) Add(key Key, value interface{}) {
|
||||
if ee, ok := c.cache.Load(key); ok {
|
||||
c.ll.MoveToFront(ee.(*list.Element)) // move to the front
|
||||
ee.(*list.Element).Value.(*entry).value = value
|
||||
return
|
||||
}
|
||||
ele := c.ll.PushFront(&entry{key, value})
|
||||
c.cache.Store(key, ele)
|
||||
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { // Remove the oldest element if the limit is exceeded
|
||||
c.RemoveOldest()
|
||||
}
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
|
||||
if ele, hit := c.cache.Load(key); hit {
|
||||
c.ll.MoveToFront(ele.(*list.Element))
|
||||
return ele.(*list.Element).Value.(*entry).value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *Cache) Remove(key Key) {
|
||||
if ele, hit := c.cache.Load(key); hit {
|
||||
c.removeElement(ele.(*list.Element))
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *Cache) RemoveOldest() {
|
||||
ele := c.ll.Back()
|
||||
if ele != nil {
|
||||
c.removeElement(ele)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) removeElement(e *list.Element) {
|
||||
c.ll.Remove(e)
|
||||
kv := e.Value.(*entry)
|
||||
c.cache.Delete(kv.key)
|
||||
if c.OnEvicted != nil {
|
||||
c.OnEvicted(kv.key, kv.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
return c.ll.Len()
|
||||
}
|
||||
|
||||
// Clear purges all stored items from the cache.
|
||||
func (c *Cache) Clear() {
|
||||
if c.OnEvicted != nil {
|
||||
c.cache.Range(func(key, value interface{}) bool {
|
||||
kv := value.(*list.Element).Value.(*entry)
|
||||
c.OnEvicted(kv.key, kv.value)
|
||||
return true
|
||||
})
|
||||
}
|
||||
c.ll = nil
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package common
|
||||
|
||||
const (
|
||||
CONN_DATA_SEQ = "*#*" //Separator
|
||||
VERIFY_EER = "vkey"
|
||||
VERIFY_SUCCESS = "sucs"
|
||||
WORK_MAIN = "main"
|
||||
WORK_CHAN = "chan"
|
||||
WORK_CONFIG = "conf"
|
||||
WORK_REGISTER = "rgst"
|
||||
WORK_SECRET = "sert"
|
||||
WORK_FILE = "file"
|
||||
WORK_P2P = "p2pm"
|
||||
WORK_P2P_VISITOR = "p2pv"
|
||||
WORK_P2P_PROVIDER = "p2pp"
|
||||
WORK_P2P_CONNECT = "p2pc"
|
||||
WORK_P2P_SUCCESS = "p2ps"
|
||||
WORK_P2P_END = "p2pe"
|
||||
WORK_P2P_LAST = "p2pl"
|
||||
WORK_STATUS = "stus"
|
||||
RES_MSG = "msg0"
|
||||
RES_CLOSE = "clse"
|
||||
NEW_UDP_CONN = "udpc" //p2p udp conn
|
||||
NEW_TASK = "task"
|
||||
NEW_CONF = "conf"
|
||||
NEW_HOST = "host"
|
||||
CONN_TCP = "tcp"
|
||||
CONN_UDP = "udp"
|
||||
CONN_TEST = "TST"
|
||||
UnauthorizedBytes = `HTTP/1.1 401 Unauthorized
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
WWW-Authenticate: Basic realm="easyProxy"
|
||||
|
||||
401 Unauthorized`
|
||||
ConnectionFailBytes = `HTTP/1.1 404 Not Found
|
||||
|
||||
`
|
||||
)
|
@ -1,48 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
"time"
|
||||
)
|
||||
|
||||
const MaxMsgLen = 5000
|
||||
|
||||
var logMsgs string
|
||||
|
||||
func init() {
|
||||
logs.Register("store", func() logs.Logger {
|
||||
return new(StoreMsg)
|
||||
})
|
||||
}
|
||||
|
||||
func GetLogMsg() string {
|
||||
return logMsgs
|
||||
}
|
||||
|
||||
type StoreMsg struct {
|
||||
}
|
||||
|
||||
func (lg *StoreMsg) Init(config string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lg *StoreMsg) WriteMsg(when time.Time, msg string, level int) error {
|
||||
m := when.Format("2006-01-02 15:04:05") + " " + msg + "\r\n"
|
||||
if len(logMsgs) > MaxMsgLen {
|
||||
start := MaxMsgLen - len(m)
|
||||
if start <= 0 {
|
||||
start = MaxMsgLen
|
||||
}
|
||||
logMsgs = logMsgs[start:]
|
||||
}
|
||||
logMsgs += m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lg *StoreMsg) Destroy() {
|
||||
return
|
||||
}
|
||||
|
||||
func (lg *StoreMsg) Flush() {
|
||||
return
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type NetPackager interface {
|
||||
Pack(writer io.Writer) (err error)
|
||||
UnPack(reader io.Reader) (err error)
|
||||
}
|
||||
|
||||
const (
|
||||
ipV4 = 1
|
||||
domainName = 3
|
||||
ipV6 = 4
|
||||
)
|
||||
|
||||
type UDPHeader struct {
|
||||
Rsv uint16
|
||||
Frag uint8
|
||||
Addr *Addr
|
||||
}
|
||||
|
||||
func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader {
|
||||
return &UDPHeader{
|
||||
Rsv: rsv,
|
||||
Frag: frag,
|
||||
Addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
type Addr struct {
|
||||
Type uint8
|
||||
Host string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func (addr *Addr) String() string {
|
||||
return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))
|
||||
}
|
||||
|
||||
func (addr *Addr) Decode(b []byte) error {
|
||||
addr.Type = b[0]
|
||||
pos := 1
|
||||
switch addr.Type {
|
||||
case ipV4:
|
||||
addr.Host = net.IP(b[pos : pos+net.IPv4len]).String()
|
||||
pos += net.IPv4len
|
||||
case ipV6:
|
||||
addr.Host = net.IP(b[pos : pos+net.IPv6len]).String()
|
||||
pos += net.IPv6len
|
||||
case domainName:
|
||||
addrlen := int(b[pos])
|
||||
pos++
|
||||
addr.Host = string(b[pos : pos+addrlen])
|
||||
pos += addrlen
|
||||
default:
|
||||
return errors.New("decode error")
|
||||
}
|
||||
|
||||
addr.Port = binary.BigEndian.Uint16(b[pos:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (addr *Addr) Encode(b []byte) (int, error) {
|
||||
b[0] = addr.Type
|
||||
pos := 1
|
||||
switch addr.Type {
|
||||
case ipV4:
|
||||
ip4 := net.ParseIP(addr.Host).To4()
|
||||
if ip4 == nil {
|
||||
ip4 = net.IPv4zero.To4()
|
||||
}
|
||||
pos += copy(b[pos:], ip4)
|
||||
case domainName:
|
||||
b[pos] = byte(len(addr.Host))
|
||||
pos++
|
||||
pos += copy(b[pos:], []byte(addr.Host))
|
||||
case ipV6:
|
||||
ip16 := net.ParseIP(addr.Host).To16()
|
||||
if ip16 == nil {
|
||||
ip16 = net.IPv6zero.To16()
|
||||
}
|
||||
pos += copy(b[pos:], ip16)
|
||||
default:
|
||||
b[0] = ipV4
|
||||
copy(b[pos:pos+4], net.IPv4zero.To4())
|
||||
pos += 4
|
||||
}
|
||||
binary.BigEndian.PutUint16(b[pos:], addr.Port)
|
||||
pos += 2
|
||||
|
||||
return pos, nil
|
||||
}
|
||||
|
||||
func (h *UDPHeader) Write(w io.Writer) error {
|
||||
b := BufPoolUdp.Get().([]byte)
|
||||
defer BufPoolUdp.Put(b)
|
||||
|
||||
binary.BigEndian.PutUint16(b[:2], h.Rsv)
|
||||
b[2] = h.Frag
|
||||
|
||||
addr := h.Addr
|
||||
if addr == nil {
|
||||
addr = &Addr{}
|
||||
}
|
||||
length, _ := addr.Encode(b[3:])
|
||||
|
||||
_, err := w.Write(b[:3+length])
|
||||
return err
|
||||
}
|
||||
|
||||
type UDPDatagram struct {
|
||||
Header *UDPHeader
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) {
|
||||
b := BufPoolUdp.Get().([]byte)
|
||||
defer BufPoolUdp.Put(b)
|
||||
|
||||
// when r is a streaming (such as TCP connection), we may read more than the required data,
|
||||
// but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast
|
||||
// to make sure that no redundant data will be discarded.
|
||||
n, err := io.ReadFull(r, b[:5])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := &UDPHeader{
|
||||
Rsv: binary.BigEndian.Uint16(b[:2]),
|
||||
Frag: b[2],
|
||||
}
|
||||
|
||||
atype := b[3]
|
||||
hlen := 0
|
||||
switch atype {
|
||||
case ipV4:
|
||||
hlen = 10
|
||||
case ipV6:
|
||||
hlen = 22
|
||||
case domainName:
|
||||
hlen = 7 + int(b[4])
|
||||
default:
|
||||
return nil, errors.New("addr not support")
|
||||
}
|
||||
dlen := int(header.Rsv)
|
||||
if dlen == 0 { // standard SOCKS5 UDP datagram
|
||||
extra, err := ioutil.ReadAll(r) // we assume no redundant data
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[n:], extra)
|
||||
n += len(extra) // total length
|
||||
dlen = n - hlen // data length
|
||||
} else { // extended feature, for UDP over TCP, using reserved field as data length
|
||||
if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n = hlen + dlen
|
||||
}
|
||||
header.Addr = new(Addr)
|
||||
if err := header.Addr.Decode(b[3:hlen]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]byte, dlen)
|
||||
copy(data, b[hlen:n])
|
||||
d := &UDPDatagram{
|
||||
Header: header,
|
||||
Data: data,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram {
|
||||
return &UDPDatagram{
|
||||
Header: header,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *UDPDatagram) Write(w io.Writer) error {
|
||||
h := d.Header
|
||||
if h == nil {
|
||||
h = &UDPHeader{}
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
if err := h.Write(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := buf.Write(d.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := buf.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
func ToSocksAddr(addr net.Addr) *Addr {
|
||||
host := "0.0.0.0"
|
||||
port := 0
|
||||
if addr != nil {
|
||||
h, p, _ := net.SplitHostPort(addr.String())
|
||||
host = h
|
||||
port, _ = strconv.Atoi(p)
|
||||
}
|
||||
return &Addr{
|
||||
Type: ipV4,
|
||||
Host: host,
|
||||
Port: uint16(port),
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
const PoolSize = 64 * 1024
|
||||
const PoolSizeSmall = 100
|
||||
const PoolSizeUdp = 1472 + 200
|
||||
const PoolSizeCopy = 32 << 10
|
||||
|
||||
var BufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
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)
|
||||
},
|
||||
}
|
||||
var BufPoolCopy = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, PoolSizeCopy)
|
||||
},
|
||||
}
|
||||
|
||||
func PutBufPoolUdp(buf []byte) {
|
||||
if cap(buf) == PoolSizeUdp {
|
||||
BufPoolUdp.Put(buf[:PoolSizeUdp])
|
||||
}
|
||||
}
|
||||
|
||||
func PutBufPoolCopy(buf []byte) {
|
||||
if cap(buf) == PoolSizeCopy {
|
||||
BufPoolCopy.Put(buf[:PoolSizeCopy])
|
||||
}
|
||||
}
|
||||
|
||||
func GetBufPoolCopy() []byte {
|
||||
return (BufPoolCopy.Get().([]byte))[:PoolSizeCopy]
|
||||
}
|
||||
|
||||
func PutBufPoolMax(buf []byte) {
|
||||
if cap(buf) == PoolSize {
|
||||
BufPoolMax.Put(buf[:PoolSize])
|
||||
}
|
||||
}
|
||||
|
||||
type copyBufferPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (Self *copyBufferPool) New() {
|
||||
Self.pool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, PoolSizeCopy, PoolSizeCopy)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (Self *copyBufferPool) Get() []byte {
|
||||
buf := Self.pool.Get().([]byte)
|
||||
return buf[:PoolSizeCopy] // just like make a new slice, but data may not be 0
|
||||
}
|
||||
|
||||
func (Self *copyBufferPool) Put(x []byte) {
|
||||
if len(x) == PoolSizeCopy {
|
||||
Self.pool.Put(x)
|
||||
} else {
|
||||
x = nil // buf is not full, not allowed, New method returns a full buf
|
||||
}
|
||||
}
|
||||
|
||||
var once = sync.Once{}
|
||||
var CopyBuff = copyBufferPool{}
|
||||
|
||||
func newPool() {
|
||||
CopyBuff.New()
|
||||
}
|
||||
|
||||
func init() {
|
||||
once.Do(newPool)
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func InitPProfFromFile() {
|
||||
ip := beego.AppConfig.String("pprof_ip")
|
||||
p := beego.AppConfig.String("pprof_port")
|
||||
if len(ip) > 0 && len(p) > 0 && IsPort(p) {
|
||||
runPProf(ip + ":" + p)
|
||||
}
|
||||
}
|
||||
|
||||
func InitPProfFromArg(arg string) {
|
||||
if len(arg) > 0 {
|
||||
runPProf(arg)
|
||||
}
|
||||
}
|
||||
|
||||
func runPProf(ipPort string) {
|
||||
go func() {
|
||||
_ = http.ListenAndServe(ipPort, nil)
|
||||
}()
|
||||
logs.Info("PProf debug listen on", ipPort)
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
//Get the currently selected configuration file directory
|
||||
//For non-Windows systems, select the /etc/nps as config directory if exist, or select ./
|
||||
//windows system, select the C:\Program Files\nps as config directory if exist, or select ./
|
||||
func GetRunPath() string {
|
||||
var path string
|
||||
if path = GetInstallPath(); !FileExists(path) {
|
||||
return GetAppPath()
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
//Different systems get different installation paths
|
||||
func GetInstallPath() string {
|
||||
var path string
|
||||
if IsWindows() {
|
||||
path = `C:\Program Files\nps`
|
||||
} else {
|
||||
path = "/etc/nps"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
//Get the absolute path to the running directory
|
||||
func GetAppPath() string {
|
||||
if path, err := filepath.Abs(filepath.Dir(os.Args[0])); err == nil {
|
||||
return path
|
||||
}
|
||||
return os.Args[0]
|
||||
}
|
||||
|
||||
//Determine whether the current system is a Windows system?
|
||||
func IsWindows() bool {
|
||||
if runtime.GOOS == "windows" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//interface log file path
|
||||
func GetLogPath() string {
|
||||
var path string
|
||||
if IsWindows() {
|
||||
path = filepath.Join(GetAppPath(), "nps.log")
|
||||
} else {
|
||||
path = "/var/log/nps.log"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
//interface npc log file path
|
||||
func GetNpcLogPath() string {
|
||||
var path string
|
||||
if IsWindows() {
|
||||
path = filepath.Join(GetAppPath(), "npc.log")
|
||||
} else {
|
||||
path = "/var/log/npc.log"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
//interface pid file path
|
||||
func GetTmpPath() string {
|
||||
var path string
|
||||
if IsWindows() {
|
||||
path = GetAppPath()
|
||||
} else {
|
||||
path = "/tmp"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
//config file path
|
||||
func GetConfigPath() string {
|
||||
var path string
|
||||
if IsWindows() {
|
||||
path = filepath.Join(GetAppPath(), "conf/npc.conf")
|
||||
} else {
|
||||
path = "conf/npc.conf"
|
||||
}
|
||||
return path
|
||||
}
|
@ -1,469 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ehang.io/nps/lib/version"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"ehang.io/nps/lib/crypt"
|
||||
)
|
||||
|
||||
//Get the corresponding IP address through domain name
|
||||
func GetHostByName(hostname string) string {
|
||||
if !DomainCheck(hostname) {
|
||||
return hostname
|
||||
}
|
||||
ips, _ := net.LookupIP(hostname)
|
||||
if ips != nil {
|
||||
for _, v := range ips {
|
||||
if v.To4() != nil {
|
||||
return v.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//Check the legality of domain
|
||||
func DomainCheck(domain string) bool {
|
||||
var match bool
|
||||
IsLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/)"
|
||||
NotLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}"
|
||||
match, _ = regexp.MatchString(IsLine, domain)
|
||||
if !match {
|
||||
match, _ = regexp.MatchString(NotLine, domain)
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
//Check if the Request request is validated
|
||||
func CheckAuth(r *http.Request, user, passwd string) bool {
|
||||
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
if len(s) != 2 {
|
||||
s = strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2)
|
||||
if len(s) != 2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
return false
|
||||
}
|
||||
return pair[0] == user && pair[1] == passwd
|
||||
}
|
||||
|
||||
//get bool by str
|
||||
func GetBoolByStr(s string) bool {
|
||||
switch s {
|
||||
case "1", "true":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//get str by bool
|
||||
func GetStrByBool(b bool) string {
|
||||
if b {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
//int
|
||||
func GetIntNoErrByStr(str string) int {
|
||||
i, _ := strconv.Atoi(strings.TrimSpace(str))
|
||||
return i
|
||||
}
|
||||
|
||||
//Get verify value
|
||||
func Getverifyval(vkey string) string {
|
||||
return crypt.Md5(vkey)
|
||||
}
|
||||
|
||||
//Change headers and host of request
|
||||
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string, addOrigin bool) {
|
||||
if host != "" {
|
||||
r.Host = host
|
||||
}
|
||||
if header != "" {
|
||||
h := strings.Split(header, "\n")
|
||||
for _, v := range h {
|
||||
hd := strings.Split(v, ":")
|
||||
if len(hd) == 2 {
|
||||
r.Header.Set(hd[0], hd[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
addr = strings.Split(addr, ":")[0]
|
||||
if prior, ok := r.Header["X-Forwarded-For"]; ok {
|
||||
addr = strings.Join(prior, ", ") + ", " + addr
|
||||
}
|
||||
if addOrigin {
|
||||
r.Header.Set("X-Forwarded-For", addr)
|
||||
r.Header.Set("X-Real-IP", addr)
|
||||
}
|
||||
}
|
||||
|
||||
//Read file content by file path
|
||||
func ReadAllFromFile(filePath string) ([]byte, error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return ioutil.ReadAll(f)
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//Judge whether the TCP port can open normally
|
||||
func TestTcpPort(port int) bool {
|
||||
l, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), port, ""})
|
||||
defer func() {
|
||||
if l != nil {
|
||||
l.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//Judge whether the UDP port can open normally
|
||||
func TestUdpPort(port int) bool {
|
||||
l, err := net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), port, ""})
|
||||
defer func() {
|
||||
if l != nil {
|
||||
l.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//Write length and individual byte data
|
||||
//Length prevents sticking
|
||||
//# Characters are used to separate data
|
||||
func BinaryWrite(raw *bytes.Buffer, v ...string) {
|
||||
b := GetWriteStr(v...)
|
||||
binary.Write(raw, binary.LittleEndian, int32(len(b)))
|
||||
binary.Write(raw, binary.LittleEndian, b)
|
||||
}
|
||||
|
||||
// get seq str
|
||||
func GetWriteStr(v ...string) []byte {
|
||||
buffer := new(bytes.Buffer)
|
||||
var l int32
|
||||
for _, v := range v {
|
||||
l += int32(len([]byte(v))) + int32(len([]byte(CONN_DATA_SEQ)))
|
||||
binary.Write(buffer, binary.LittleEndian, []byte(v))
|
||||
binary.Write(buffer, binary.LittleEndian, []byte(CONN_DATA_SEQ))
|
||||
}
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
//inArray str interface
|
||||
func InStrArr(arr []string, val string) bool {
|
||||
for _, v := range arr {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//inArray int interface
|
||||
func InIntArr(arr []int, val int) bool {
|
||||
for _, v := range arr {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//format ports str to a int array
|
||||
func GetPorts(p string) []int {
|
||||
var ps []int
|
||||
arr := strings.Split(p, ",")
|
||||
for _, v := range arr {
|
||||
fw := strings.Split(v, "-")
|
||||
if len(fw) == 2 {
|
||||
if IsPort(fw[0]) && IsPort(fw[1]) {
|
||||
start, _ := strconv.Atoi(fw[0])
|
||||
end, _ := strconv.Atoi(fw[1])
|
||||
for i := start; i <= end; i++ {
|
||||
ps = append(ps, i)
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if IsPort(v) {
|
||||
p, _ := strconv.Atoi(v)
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
//is the string a port
|
||||
func IsPort(p string) bool {
|
||||
pi, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if pi > 65536 || pi < 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//if the s is just a port,return 127.0.0.1:s
|
||||
func FormatAddress(s string) string {
|
||||
if strings.Contains(s, ":") {
|
||||
return s
|
||||
}
|
||||
return "127.0.0.1:" + s
|
||||
}
|
||||
|
||||
//get address from the complete address
|
||||
func GetIpByAddr(addr string) string {
|
||||
arr := strings.Split(addr, ":")
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
//get port from the complete address
|
||||
func GetPortByAddr(addr string) int {
|
||||
arr := strings.Split(addr, ":")
|
||||
if len(arr) < 2 {
|
||||
return 0
|
||||
}
|
||||
p, err := strconv.Atoi(arr[1])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func CopyBuffer(dst io.Writer, src io.Reader, label ...string) (written int64, err error) {
|
||||
buf := CopyBuff.Get()
|
||||
defer CopyBuff.Put(buf)
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
//if len(pr)>0 && pr[0] && nr > 50 {
|
||||
// logs.Warn(string(buf[:50]))
|
||||
//}
|
||||
if nr > 0 {
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er != nil {
|
||||
err = er
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
|
||||
//send this ip forget to get a local udp port
|
||||
func GetLocalUdpAddr() (net.Conn, error) {
|
||||
tmpConn, err := net.Dial("udp", "114.114.114.114:53")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tmpConn, tmpConn.Close()
|
||||
}
|
||||
|
||||
//parse template
|
||||
func ParseStr(str string) (string, error) {
|
||||
tmp := template.New("npc")
|
||||
var err error
|
||||
w := new(bytes.Buffer)
|
||||
if tmp, err = tmp.Parse(str); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = tmp.Execute(w, GetEnvMap()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
//get env
|
||||
func GetEnvMap() map[string]string {
|
||||
m := make(map[string]string)
|
||||
environ := os.Environ()
|
||||
for i := range environ {
|
||||
tmp := strings.Split(environ[i], "=")
|
||||
if len(tmp) == 2 {
|
||||
m[tmp[0]] = tmp[1]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//throw the empty element of the string array
|
||||
func TrimArr(arr []string) []string {
|
||||
newArr := make([]string, 0)
|
||||
for _, v := range arr {
|
||||
if v != "" {
|
||||
newArr = append(newArr, v)
|
||||
}
|
||||
}
|
||||
return newArr
|
||||
}
|
||||
|
||||
//
|
||||
func IsArrContains(arr []string, val string) bool {
|
||||
if arr == nil {
|
||||
return false
|
||||
}
|
||||
for _, v := range arr {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//remove value from string array
|
||||
func RemoveArrVal(arr []string, val string) []string {
|
||||
for k, v := range arr {
|
||||
if v == val {
|
||||
arr = append(arr[:k], arr[k+1:]...)
|
||||
return arr
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
//convert bytes to num
|
||||
func BytesToNum(b []byte) int {
|
||||
var str string
|
||||
for i := 0; i < len(b); i++ {
|
||||
str += strconv.Itoa(int(b[i]))
|
||||
}
|
||||
x, _ := strconv.Atoi(str)
|
||||
return int(x)
|
||||
}
|
||||
|
||||
//get the length of the sync map
|
||||
func GeSynctMapLen(m sync.Map) int {
|
||||
var c int
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
c++
|
||||
return true
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
func GetExtFromPath(path string) string {
|
||||
s := strings.Split(path, ".")
|
||||
re, err := regexp.Compile(`(\w+)`)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(re.Find([]byte(s[0])))
|
||||
}
|
||||
|
||||
var externalIp string
|
||||
|
||||
func GetExternalIp() string {
|
||||
if externalIp != "" {
|
||||
return externalIp
|
||||
}
|
||||
resp, err := http.Get("http://myexternalip.com/raw")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
content, _ := ioutil.ReadAll(resp.Body)
|
||||
externalIp = string(content)
|
||||
return externalIp
|
||||
}
|
||||
|
||||
func GetIntranetIp() (error, string) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, ""
|
||||
}
|
||||
for _, address := range addrs {
|
||||
// 检查ip地址判断是否回环地址
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return nil, ipnet.IP.To4().String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.New("get intranet ip error"), ""
|
||||
}
|
||||
|
||||
func IsPublicIP(IP net.IP) bool {
|
||||
if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
|
||||
return false
|
||||
}
|
||||
if ip4 := IP.To4(); ip4 != nil {
|
||||
switch true {
|
||||
case ip4[0] == 10:
|
||||
return false
|
||||
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
|
||||
return false
|
||||
case ip4[0] == 192 && ip4[1] == 168:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetServerIpByClientIp(clientIp net.IP) string {
|
||||
if IsPublicIP(clientIp) {
|
||||
return GetExternalIp()
|
||||
}
|
||||
_, ip := GetIntranetIp()
|
||||
return ip
|
||||
}
|
||||
|
||||
func PrintVersion() {
|
||||
fmt.Printf("Version: %s\nCore version: %s\nSame core version of client and server can connect each other\n", version.VERSION, version.GetVersion())
|
||||
}
|
@ -1,329 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/file"
|
||||
)
|
||||
|
||||
type CommonConfig struct {
|
||||
Server string
|
||||
VKey string
|
||||
Tp string //bridgeType kcp or tcp
|
||||
AutoReconnection bool
|
||||
ProxyUrl string
|
||||
Client *file.Client
|
||||
DisconnectTime int
|
||||
}
|
||||
|
||||
type LocalServer struct {
|
||||
Type string
|
||||
Port int
|
||||
Ip string
|
||||
Password string
|
||||
Target string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
content string
|
||||
title []string
|
||||
CommonConfig *CommonConfig
|
||||
Hosts []*file.Host
|
||||
Tasks []*file.Tunnel
|
||||
Healths []*file.Health
|
||||
LocalServer []*LocalServer
|
||||
}
|
||||
|
||||
func NewConfig(path string) (c *Config, err error) {
|
||||
c = new(Config)
|
||||
var b []byte
|
||||
if b, err = common.ReadAllFromFile(path); err != nil {
|
||||
return
|
||||
} else {
|
||||
if c.content, err = common.ParseStr(string(b)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.title, err = getAllTitle(c.content); err != nil {
|
||||
return
|
||||
}
|
||||
var nowIndex int
|
||||
var nextIndex int
|
||||
var nowContent string
|
||||
for i := 0; i < len(c.title); i++ {
|
||||
nowIndex = strings.Index(c.content, c.title[i]) + len(c.title[i])
|
||||
if i < len(c.title)-1 {
|
||||
nextIndex = strings.Index(c.content, c.title[i+1])
|
||||
} else {
|
||||
nextIndex = len(c.content)
|
||||
}
|
||||
nowContent = c.content[nowIndex:nextIndex]
|
||||
|
||||
if strings.Index(getTitleContent(c.title[i]), "secret") == 0 && !strings.Contains(nowContent, "mode") {
|
||||
local := delLocalService(nowContent)
|
||||
local.Type = "secret"
|
||||
c.LocalServer = append(c.LocalServer, local)
|
||||
continue
|
||||
}
|
||||
//except mode
|
||||
if strings.Index(getTitleContent(c.title[i]), "p2p") == 0 && !strings.Contains(nowContent, "mode") {
|
||||
local := delLocalService(nowContent)
|
||||
local.Type = "p2p"
|
||||
c.LocalServer = append(c.LocalServer, local)
|
||||
continue
|
||||
}
|
||||
//health set
|
||||
if strings.Index(getTitleContent(c.title[i]), "health") == 0 {
|
||||
c.Healths = append(c.Healths, dealHealth(nowContent))
|
||||
continue
|
||||
}
|
||||
switch c.title[i] {
|
||||
case "[common]":
|
||||
c.CommonConfig = dealCommon(nowContent)
|
||||
default:
|
||||
if strings.Index(nowContent, "host") > -1 {
|
||||
h := dealHost(nowContent)
|
||||
h.Remark = getTitleContent(c.title[i])
|
||||
c.Hosts = append(c.Hosts, h)
|
||||
} else {
|
||||
t := dealTunnel(nowContent)
|
||||
t.Remark = getTitleContent(c.title[i])
|
||||
c.Tasks = append(c.Tasks, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getTitleContent(s string) string {
|
||||
re, _ := regexp.Compile(`[\[\]]`)
|
||||
return re.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func dealCommon(s string) *CommonConfig {
|
||||
c := &CommonConfig{}
|
||||
c.Client = file.NewClient("", true, true)
|
||||
c.Client.Cnf = new(file.Config)
|
||||
for _, v := range splitStr(s) {
|
||||
item := strings.Split(v, "=")
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
} else if len(item) == 1 {
|
||||
item = append(item, "")
|
||||
}
|
||||
switch item[0] {
|
||||
case "server_addr":
|
||||
c.Server = item[1]
|
||||
case "vkey":
|
||||
c.VKey = item[1]
|
||||
case "conn_type":
|
||||
c.Tp = item[1]
|
||||
case "auto_reconnection":
|
||||
c.AutoReconnection = common.GetBoolByStr(item[1])
|
||||
case "basic_username":
|
||||
c.Client.Cnf.U = item[1]
|
||||
case "basic_password":
|
||||
c.Client.Cnf.P = item[1]
|
||||
case "web_password":
|
||||
c.Client.WebPassword = item[1]
|
||||
case "web_username":
|
||||
c.Client.WebUserName = item[1]
|
||||
case "compress":
|
||||
c.Client.Cnf.Compress = common.GetBoolByStr(item[1])
|
||||
case "crypt":
|
||||
c.Client.Cnf.Crypt = common.GetBoolByStr(item[1])
|
||||
case "proxy_url":
|
||||
c.ProxyUrl = item[1]
|
||||
case "rate_limit":
|
||||
c.Client.RateLimit = common.GetIntNoErrByStr(item[1])
|
||||
case "flow_limit":
|
||||
c.Client.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[1]))
|
||||
case "max_conn":
|
||||
c.Client.MaxConn = common.GetIntNoErrByStr(item[1])
|
||||
case "remark":
|
||||
c.Client.Remark = item[1]
|
||||
case "pprof_addr":
|
||||
common.InitPProfFromArg(item[1])
|
||||
case "disconnect_timeout":
|
||||
c.DisconnectTime = common.GetIntNoErrByStr(item[1])
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func dealHost(s string) *file.Host {
|
||||
h := &file.Host{}
|
||||
h.Target = new(file.Target)
|
||||
h.Scheme = "all"
|
||||
var headerChange string
|
||||
for _, v := range splitStr(s) {
|
||||
item := strings.Split(v, "=")
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
} else if len(item) == 1 {
|
||||
item = append(item, "")
|
||||
}
|
||||
switch strings.TrimSpace(item[0]) {
|
||||
case "host":
|
||||
h.Host = item[1]
|
||||
case "target_addr":
|
||||
h.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1)
|
||||
case "host_change":
|
||||
h.HostChange = item[1]
|
||||
case "scheme":
|
||||
h.Scheme = item[1]
|
||||
case "location":
|
||||
h.Location = item[1]
|
||||
default:
|
||||
if strings.Contains(item[0], "header") {
|
||||
headerChange += strings.Replace(item[0], "header_", "", -1) + ":" + item[1] + "\n"
|
||||
}
|
||||
h.HeaderChange = headerChange
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func dealHealth(s string) *file.Health {
|
||||
h := &file.Health{}
|
||||
for _, v := range splitStr(s) {
|
||||
item := strings.Split(v, "=")
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
} else if len(item) == 1 {
|
||||
item = append(item, "")
|
||||
}
|
||||
switch strings.TrimSpace(item[0]) {
|
||||
case "health_check_timeout":
|
||||
h.HealthCheckTimeout = common.GetIntNoErrByStr(item[1])
|
||||
case "health_check_max_failed":
|
||||
h.HealthMaxFail = common.GetIntNoErrByStr(item[1])
|
||||
case "health_check_interval":
|
||||
h.HealthCheckInterval = common.GetIntNoErrByStr(item[1])
|
||||
case "health_http_url":
|
||||
h.HttpHealthUrl = item[1]
|
||||
case "health_check_type":
|
||||
h.HealthCheckType = item[1]
|
||||
case "health_check_target":
|
||||
h.HealthCheckTarget = item[1]
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func dealTunnel(s string) *file.Tunnel {
|
||||
t := &file.Tunnel{}
|
||||
t.Target = new(file.Target)
|
||||
for _, v := range splitStr(s) {
|
||||
item := strings.Split(v, "=")
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
} else if len(item) == 1 {
|
||||
item = append(item, "")
|
||||
}
|
||||
switch strings.TrimSpace(item[0]) {
|
||||
case "server_port":
|
||||
t.Ports = item[1]
|
||||
case "server_ip":
|
||||
t.ServerIp = item[1]
|
||||
case "mode":
|
||||
t.Mode = item[1]
|
||||
case "target_addr":
|
||||
t.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1)
|
||||
case "target_port":
|
||||
t.Target.TargetStr = item[1]
|
||||
case "target_ip":
|
||||
t.TargetAddr = item[1]
|
||||
case "password":
|
||||
t.Password = item[1]
|
||||
case "local_path":
|
||||
t.LocalPath = item[1]
|
||||
case "strip_pre":
|
||||
t.StripPre = item[1]
|
||||
case "multi_account":
|
||||
t.MultiAccount = &file.MultiAccount{}
|
||||
if common.FileExists(item[1]) {
|
||||
if b, err := common.ReadAllFromFile(item[1]); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
if content, err := common.ParseStr(string(b)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
t.MultiAccount.AccountMap = dealMultiUser(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return t
|
||||
|
||||
}
|
||||
|
||||
func dealMultiUser(s string) map[string]string {
|
||||
multiUserMap := make(map[string]string)
|
||||
for _, v := range splitStr(s) {
|
||||
item := strings.Split(v, "=")
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
} else if len(item) == 1 {
|
||||
item = append(item, "")
|
||||
}
|
||||
multiUserMap[strings.TrimSpace(item[0])] = item[1]
|
||||
}
|
||||
return multiUserMap
|
||||
}
|
||||
|
||||
func delLocalService(s string) *LocalServer {
|
||||
l := new(LocalServer)
|
||||
for _, v := range splitStr(s) {
|
||||
item := strings.Split(v, "=")
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
} else if len(item) == 1 {
|
||||
item = append(item, "")
|
||||
}
|
||||
switch item[0] {
|
||||
case "local_port":
|
||||
l.Port = common.GetIntNoErrByStr(item[1])
|
||||
case "local_ip":
|
||||
l.Ip = item[1]
|
||||
case "password":
|
||||
l.Password = item[1]
|
||||
case "target_addr":
|
||||
l.Target = item[1]
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func getAllTitle(content string) (arr []string, err error) {
|
||||
var re *regexp.Regexp
|
||||
re, err = regexp.Compile(`(?m)^\[[^\[\]\r\n]+\]`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
arr = re.FindAllString(content, -1)
|
||||
m := make(map[string]bool)
|
||||
for _, v := range arr {
|
||||
if _, ok := m[v]; ok {
|
||||
err = errors.New(fmt.Sprintf("Item names %s are not allowed to be duplicated", v))
|
||||
return
|
||||
}
|
||||
m[v] = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func splitStr(s string) (configDataArr []string) {
|
||||
if common.IsWindows() {
|
||||
configDataArr = strings.Split(s, "\r\n")
|
||||
}
|
||||
if len(configDataArr) < 3 {
|
||||
configDataArr = strings.Split(s, "\n")
|
||||
}
|
||||
return
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReg(t *testing.T) {
|
||||
content := `
|
||||
[common]
|
||||
server=127.0.0.1:8284
|
||||
tp=tcp
|
||||
vkey=123
|
||||
[web2]
|
||||
host=www.baidu.com
|
||||
host_change=www.sina.com
|
||||
target=127.0.0.1:8080,127.0.0.1:8082
|
||||
header_cookkile=122123
|
||||
header_user-Agent=122123
|
||||
[web2]
|
||||
host=www.baidu.com
|
||||
host_change=www.sina.com
|
||||
target=127.0.0.1:8080,127.0.0.1:8082
|
||||
header_cookkile="122123"
|
||||
header_user-Agent=122123
|
||||
[tunnel1]
|
||||
type=udp
|
||||
target=127.0.0.1:8080
|
||||
port=9001
|
||||
compress=snappy
|
||||
crypt=true
|
||||
u=1
|
||||
p=2
|
||||
[tunnel2]
|
||||
type=tcp
|
||||
target=127.0.0.1:8080
|
||||
port=9001
|
||||
compress=snappy
|
||||
crypt=true
|
||||
u=1
|
||||
p=2
|
||||
`
|
||||
re, err := regexp.Compile(`\[.+?\]`)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
log.Println(re.FindAllString(content, -1))
|
||||
}
|
||||
|
||||
func TestDealCommon(t *testing.T) {
|
||||
s := `server=127.0.0.1:8284
|
||||
tp=tcp
|
||||
vkey=123`
|
||||
f := new(CommonConfig)
|
||||
f.Server = "127.0.0.1:8284"
|
||||
f.Tp = "tcp"
|
||||
f.VKey = "123"
|
||||
if c := dealCommon(s); *c != *f {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTitleContent(t *testing.T) {
|
||||
s := "[common]"
|
||||
if getTitleContent(s) != "common" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
431
lib/conn/conn.go
@ -1,431 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"ehang.io/nps/lib/goroutine"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/lib/pmux"
|
||||
"ehang.io/nps/lib/rate"
|
||||
"github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
Conn net.Conn
|
||||
Rb []byte
|
||||
}
|
||||
|
||||
//new conn
|
||||
func NewConn(conn net.Conn) *Conn {
|
||||
return &Conn{Conn: conn}
|
||||
}
|
||||
|
||||
func (s *Conn) readRequest(buf []byte) (n int, err error) {
|
||||
var rd int
|
||||
for {
|
||||
rd, err = s.Read(buf[n:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n += rd
|
||||
if n < 4 {
|
||||
continue
|
||||
}
|
||||
if string(buf[n-4:n]) == "\r\n\r\n" {
|
||||
return
|
||||
}
|
||||
// buf is full, can't contain the request
|
||||
if n == cap(buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//get host 、connection type、method...from connection
|
||||
func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) {
|
||||
var b [32 * 1024]byte
|
||||
var n int
|
||||
if n, err = s.readRequest(b[:]); err != nil {
|
||||
return
|
||||
}
|
||||
rb = b[:n]
|
||||
r, err = http.ReadRequest(bufio.NewReader(bytes.NewReader(rb)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hostPortURL, err := url.Parse(r.Host)
|
||||
if err != nil {
|
||||
address = r.Host
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
if hostPortURL.Opaque == "443" {
|
||||
if strings.Index(r.Host, ":") == -1 {
|
||||
address = r.Host + ":443"
|
||||
} else {
|
||||
address = r.Host
|
||||
}
|
||||
} else {
|
||||
if strings.Index(r.Host, ":") == -1 {
|
||||
address = r.Host + ":80"
|
||||
} else {
|
||||
address = r.Host
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Conn) GetShortLenContent() (b []byte, err error) {
|
||||
var l int
|
||||
if l, err = s.GetLen(); err != nil {
|
||||
return
|
||||
}
|
||||
if l < 0 || l > 32<<10 {
|
||||
err = errors.New("read length error")
|
||||
return
|
||||
}
|
||||
return s.GetShortContent(l)
|
||||
}
|
||||
|
||||
func (s *Conn) GetShortContent(l int) (b []byte, err error) {
|
||||
buf := make([]byte, l)
|
||||
return buf, binary.Read(s, binary.LittleEndian, &buf)
|
||||
}
|
||||
|
||||
//读取指定长度内容
|
||||
func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {
|
||||
if cLen > len(buf) || cLen <= 0 {
|
||||
return 0, errors.New("长度错误" + strconv.Itoa(cLen))
|
||||
}
|
||||
if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen {
|
||||
return n, errors.New("Error reading specified length " + err.Error())
|
||||
}
|
||||
return cLen, nil
|
||||
}
|
||||
|
||||
func (s *Conn) GetLen() (int, error) {
|
||||
var l int32
|
||||
err := binary.Read(s, binary.LittleEndian, &l)
|
||||
return int(l), err
|
||||
}
|
||||
|
||||
func (s *Conn) WriteLenContent(buf []byte) (err error) {
|
||||
var b []byte
|
||||
if b, err = GetLenBytes(buf); err != nil {
|
||||
return
|
||||
}
|
||||
return binary.Write(s.Conn, binary.LittleEndian, b)
|
||||
}
|
||||
|
||||
//read flag
|
||||
func (s *Conn) ReadFlag() (string, error) {
|
||||
buf := make([]byte, 4)
|
||||
return string(buf), binary.Read(s, binary.LittleEndian, &buf)
|
||||
}
|
||||
|
||||
//set alive
|
||||
func (s *Conn) SetAlive(tp string) {
|
||||
switch s.Conn.(type) {
|
||||
case *kcp.UDPSession:
|
||||
s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Time{})
|
||||
case *net.TCPConn:
|
||||
conn := s.Conn.(*net.TCPConn)
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
//conn.SetKeepAlive(false)
|
||||
//conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
|
||||
case *pmux.PortConn:
|
||||
s.Conn.(*pmux.PortConn).SetReadDeadline(time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
//set read deadline
|
||||
func (s *Conn) SetReadDeadlineBySecond(t time.Duration) {
|
||||
switch s.Conn.(type) {
|
||||
case *kcp.UDPSession:
|
||||
s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
|
||||
case *net.TCPConn:
|
||||
s.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
|
||||
case *pmux.PortConn:
|
||||
s.Conn.(*pmux.PortConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
//get link info from conn
|
||||
func (s *Conn) GetLinkInfo() (lk *Link, err error) {
|
||||
err = s.getInfo(&lk)
|
||||
return
|
||||
}
|
||||
|
||||
//send info for link
|
||||
func (s *Conn) SendHealthInfo(info, status string) (int, error) {
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
common.BinaryWrite(raw, info, status)
|
||||
return s.Write(raw.Bytes())
|
||||
}
|
||||
|
||||
//get health info from conn
|
||||
func (s *Conn) GetHealthInfo() (info string, status bool, err error) {
|
||||
var l int
|
||||
buf := common.BufPoolMax.Get().([]byte)
|
||||
defer common.PutBufPoolMax(buf)
|
||||
if l, err = s.GetLen(); err != nil {
|
||||
return
|
||||
} else if _, err = s.ReadLen(l, buf); err != nil {
|
||||
return
|
||||
} else {
|
||||
arr := strings.Split(string(buf[:l]), common.CONN_DATA_SEQ)
|
||||
if len(arr) >= 2 {
|
||||
return arr[0], common.GetBoolByStr(arr[1]), nil
|
||||
}
|
||||
}
|
||||
return "", false, errors.New("receive health info error")
|
||||
}
|
||||
|
||||
//get task info
|
||||
func (s *Conn) GetHostInfo() (h *file.Host, err error) {
|
||||
err = s.getInfo(&h)
|
||||
h.Id = int(file.GetDb().JsonDb.GetHostId())
|
||||
h.Flow = new(file.Flow)
|
||||
h.NoStore = true
|
||||
return
|
||||
}
|
||||
|
||||
//get task info
|
||||
func (s *Conn) GetConfigInfo() (c *file.Client, err error) {
|
||||
err = s.getInfo(&c)
|
||||
c.NoStore = true
|
||||
c.Status = true
|
||||
if c.Flow == nil {
|
||||
c.Flow = new(file.Flow)
|
||||
}
|
||||
c.NoDisplay = false
|
||||
return
|
||||
}
|
||||
|
||||
//get task info
|
||||
func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) {
|
||||
err = s.getInfo(&t)
|
||||
t.Id = int(file.GetDb().JsonDb.GetTaskId())
|
||||
t.NoStore = true
|
||||
t.Flow = new(file.Flow)
|
||||
return
|
||||
}
|
||||
|
||||
//send info
|
||||
func (s *Conn) SendInfo(t interface{}, flag string) (int, error) {
|
||||
/*
|
||||
The task info is formed as follows:
|
||||
+----+-----+---------+
|
||||
|type| len | content |
|
||||
+----+---------------+
|
||||
| 4 | 4 | ... |
|
||||
+----+---------------+
|
||||
*/
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
if flag != "" {
|
||||
binary.Write(raw, binary.LittleEndian, []byte(flag))
|
||||
}
|
||||
b, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lenBytes, err := GetLenBytes(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
binary.Write(raw, binary.LittleEndian, lenBytes)
|
||||
return s.Write(raw.Bytes())
|
||||
}
|
||||
|
||||
//get task info
|
||||
func (s *Conn) getInfo(t interface{}) (err error) {
|
||||
var l int
|
||||
buf := common.BufPoolMax.Get().([]byte)
|
||||
defer common.PutBufPoolMax(buf)
|
||||
if l, err = s.GetLen(); err != nil {
|
||||
return
|
||||
} else if _, err = s.ReadLen(l, buf); err != nil {
|
||||
return
|
||||
} else {
|
||||
json.Unmarshal(buf[:l], &t)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//close
|
||||
func (s *Conn) Close() error {
|
||||
return s.Conn.Close()
|
||||
}
|
||||
|
||||
//write
|
||||
func (s *Conn) Write(b []byte) (int, error) {
|
||||
return s.Conn.Write(b)
|
||||
}
|
||||
|
||||
//read
|
||||
func (s *Conn) Read(b []byte) (n int, err error) {
|
||||
if s.Rb != nil {
|
||||
//if the rb is not nil ,read rb first
|
||||
if len(s.Rb) > 0 {
|
||||
n = copy(b, s.Rb)
|
||||
s.Rb = s.Rb[n:]
|
||||
return
|
||||
}
|
||||
s.Rb = nil
|
||||
}
|
||||
return s.Conn.Read(b)
|
||||
}
|
||||
|
||||
//write sign flag
|
||||
func (s *Conn) WriteClose() (int, error) {
|
||||
return s.Write([]byte(common.RES_CLOSE))
|
||||
}
|
||||
|
||||
//write main
|
||||
func (s *Conn) WriteMain() (int, error) {
|
||||
return s.Write([]byte(common.WORK_MAIN))
|
||||
}
|
||||
|
||||
//write main
|
||||
func (s *Conn) WriteConfig() (int, error) {
|
||||
return s.Write([]byte(common.WORK_CONFIG))
|
||||
}
|
||||
|
||||
//write chan
|
||||
func (s *Conn) WriteChan() (int, error) {
|
||||
return s.Write([]byte(common.WORK_CHAN))
|
||||
}
|
||||
|
||||
//get task or host result of add
|
||||
func (s *Conn) GetAddStatus() (b bool) {
|
||||
binary.Read(s.Conn, binary.LittleEndian, &b)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Conn) WriteAddOk() error {
|
||||
return binary.Write(s.Conn, binary.LittleEndian, true)
|
||||
}
|
||||
|
||||
func (s *Conn) WriteAddFail() error {
|
||||
defer s.Close()
|
||||
return binary.Write(s.Conn, binary.LittleEndian, false)
|
||||
}
|
||||
|
||||
func (s *Conn) LocalAddr() net.Addr {
|
||||
return s.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *Conn) RemoteAddr() net.Addr {
|
||||
return s.Conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (s *Conn) SetDeadline(t time.Time) error {
|
||||
return s.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return s.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Conn) SetReadDeadline(t time.Time) error {
|
||||
return s.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
//get the assembled amount data(len 4 and content)
|
||||
func GetLenBytes(buf []byte) (b []byte, err error) {
|
||||
raw := bytes.NewBuffer([]byte{})
|
||||
if err = binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(raw, binary.LittleEndian, buf); err != nil {
|
||||
return
|
||||
}
|
||||
b = raw.Bytes()
|
||||
return
|
||||
}
|
||||
|
||||
//udp connection setting
|
||||
func SetUdpSession(sess *kcp.UDPSession) {
|
||||
sess.SetStreamMode(true)
|
||||
sess.SetWindowSize(1024, 1024)
|
||||
sess.SetReadBuffer(64 * 1024)
|
||||
sess.SetWriteBuffer(64 * 1024)
|
||||
sess.SetNoDelay(1, 10, 2, 1)
|
||||
sess.SetMtu(1600)
|
||||
sess.SetACKNoDelay(true)
|
||||
sess.SetWriteDelay(false)
|
||||
}
|
||||
|
||||
//conn1 mux conn
|
||||
func CopyWaitGroup(conn1, conn2 net.Conn, crypt bool, snappy bool, rate *rate.Rate, flow *file.Flow, isServer bool, rb []byte) {
|
||||
//var in, out int64
|
||||
//var wg sync.WaitGroup
|
||||
connHandle := GetConn(conn1, crypt, snappy, rate, isServer)
|
||||
if rb != nil {
|
||||
connHandle.Write(rb)
|
||||
}
|
||||
//go func(in *int64) {
|
||||
// wg.Add(1)
|
||||
// *in, _ = common.CopyBuffer(connHandle, conn2)
|
||||
// connHandle.Close()
|
||||
// conn2.Close()
|
||||
// wg.Done()
|
||||
//}(&in)
|
||||
//out, _ = common.CopyBuffer(conn2, connHandle)
|
||||
//connHandle.Close()
|
||||
//conn2.Close()
|
||||
//wg.Wait()
|
||||
//if flow != nil {
|
||||
// flow.Add(in, out)
|
||||
//}
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
err := goroutine.CopyConnsPool.Invoke(goroutine.NewConns(connHandle, conn2, flow, wg))
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
//get crypt or snappy conn
|
||||
func GetConn(conn net.Conn, cpt, snappy bool, rt *rate.Rate, isServer bool) io.ReadWriteCloser {
|
||||
if cpt {
|
||||
if isServer {
|
||||
return rate.NewRateConn(crypt.NewTlsServerConn(conn), rt)
|
||||
}
|
||||
return rate.NewRateConn(crypt.NewTlsClientConn(conn), rt)
|
||||
} else if snappy {
|
||||
return rate.NewRateConn(NewSnappyConn(conn), rt)
|
||||
}
|
||||
return rate.NewRateConn(conn, rt)
|
||||
}
|
||||
|
||||
type LenConn struct {
|
||||
conn io.Writer
|
||||
Len int
|
||||
}
|
||||
|
||||
func NewLenConn(conn io.Writer) *LenConn {
|
||||
return &LenConn{conn: conn}
|
||||
}
|
||||
func (c *LenConn) Write(p []byte) (n int, err error) {
|
||||
n, err = c.conn.Write(p)
|
||||
c.Len += n
|
||||
return
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package conn
|
||||
|
||||
import "time"
|
||||
|
||||
type Secret struct {
|
||||
Password string
|
||||
Conn *Conn
|
||||
}
|
||||
|
||||
func NewSecret(p string, conn *Conn) *Secret {
|
||||
return &Secret{
|
||||
Password: p,
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
ConnType string //连接类型
|
||||
Host string //目标
|
||||
Crypt bool //加密
|
||||
Compress bool
|
||||
LocalProxy bool
|
||||
RemoteAddr string
|
||||
Option Options
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type Options struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
var defaultTimeOut = time.Second * 5
|
||||
|
||||
func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool, opts ...Option) *Link {
|
||||
options := newOptions(opts...)
|
||||
|
||||
return &Link{
|
||||
RemoteAddr: remoteAddr,
|
||||
ConnType: connType,
|
||||
Host: host,
|
||||
Crypt: crypt,
|
||||
Compress: compress,
|
||||
LocalProxy: localProxy,
|
||||
Option: options,
|
||||
}
|
||||
}
|
||||
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{
|
||||
Timeout: defaultTimeOut,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
func LinkTimeout(t time.Duration) Option {
|
||||
return func(opt *Options) {
|
||||
opt.Timeout = t
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
func NewTcpListenerAndProcess(addr string, f func(c net.Conn), listener *net.Listener) error {
|
||||
var err error
|
||||
*listener, err = net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Accept(*listener, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewKcpListenerAndProcess(addr string, f func(c net.Conn)) error {
|
||||
kcpListener, err := kcp.ListenWithOptions(addr, nil, 150, 3)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return err
|
||||
}
|
||||
for {
|
||||
c, err := kcpListener.AcceptKCP()
|
||||
SetUdpSession(c)
|
||||
if err != nil {
|
||||
logs.Warn(err)
|
||||
continue
|
||||
}
|
||||
go f(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Accept(l net.Listener, f func(c net.Conn)) {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
break
|
||||
}
|
||||
if strings.Contains(err.Error(), "the mux has closed") {
|
||||
break
|
||||
}
|
||||
logs.Warn(err)
|
||||
continue
|
||||
}
|
||||
if c == nil {
|
||||
logs.Warn("nil connection")
|
||||
break
|
||||
}
|
||||
go f(c)
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
type SnappyConn struct {
|
||||
w *snappy.Writer
|
||||
r *snappy.Reader
|
||||
c io.Closer
|
||||
}
|
||||
|
||||
func NewSnappyConn(conn io.ReadWriteCloser) *SnappyConn {
|
||||
c := new(SnappyConn)
|
||||
c.w = snappy.NewBufferedWriter(conn)
|
||||
c.r = snappy.NewReader(conn)
|
||||
c.c = conn.(io.Closer)
|
||||
return c
|
||||
}
|
||||
|
||||
//snappy压缩写
|
||||
func (s *SnappyConn) Write(b []byte) (n int, err error) {
|
||||
if n, err = s.w.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.w.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//snappy压缩读
|
||||
func (s *SnappyConn) Read(b []byte) (n int, err error) {
|
||||
return s.r.Read(b)
|
||||
}
|
||||
|
||||
func (s *SnappyConn) Close() error {
|
||||
err := s.w.Close()
|
||||
err2 := s.c.Close()
|
||||
if err != nil && err2 == nil {
|
||||
return err
|
||||
}
|
||||
if err == nil && err2 != nil {
|
||||
return err2
|
||||
}
|
||||
if err != nil && err2 != nil {
|
||||
return errors.New(err.Error() + err2.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CurveID uint16
|
||||
type SignatureScheme uint16
|
||||
|
||||
const (
|
||||
statusTypeOCSP uint8 = 1
|
||||
extensionServerName uint16 = 0
|
||||
extensionStatusRequest uint16 = 5
|
||||
extensionSupportedCurves uint16 = 10
|
||||
extensionSupportedPoints uint16 = 11
|
||||
extensionSignatureAlgorithms uint16 = 13
|
||||
extensionALPN uint16 = 16
|
||||
extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6
|
||||
extensionSessionTicket uint16 = 35
|
||||
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
|
||||
extensionRenegotiationInfo uint16 = 0xff01
|
||||
scsvRenegotiation uint16 = 0x00ff
|
||||
)
|
||||
|
||||
type ClientHelloMsg struct {
|
||||
raw []byte
|
||||
vers uint16
|
||||
random []byte
|
||||
sessionId []byte
|
||||
cipherSuites []uint16
|
||||
compressionMethods []uint8
|
||||
nextProtoNeg bool
|
||||
serverName string
|
||||
ocspStapling bool
|
||||
scts bool
|
||||
supportedCurves []CurveID
|
||||
supportedPoints []uint8
|
||||
ticketSupported bool
|
||||
sessionTicket []uint8
|
||||
supportedSignatureAlgorithms []SignatureScheme
|
||||
secureRenegotiation []byte
|
||||
secureRenegotiationSupported bool
|
||||
alpnProtocols []string
|
||||
}
|
||||
|
||||
func (m *ClientHelloMsg) GetServerName() string {
|
||||
return m.serverName
|
||||
}
|
||||
|
||||
func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
|
||||
if len(data) < 42 {
|
||||
return false
|
||||
}
|
||||
m.raw = data
|
||||
m.vers = uint16(data[4])<<8 | uint16(data[5])
|
||||
m.random = data[6:38]
|
||||
sessionIdLen := int(data[38])
|
||||
if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
|
||||
return false
|
||||
}
|
||||
m.sessionId = data[39 : 39+sessionIdLen]
|
||||
data = data[39+sessionIdLen:]
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
|
||||
// they are uint16s, the number must be even.
|
||||
cipherSuiteLen := int(data[0])<<8 | int(data[1])
|
||||
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
|
||||
return false
|
||||
}
|
||||
numCipherSuites := cipherSuiteLen / 2
|
||||
m.cipherSuites = make([]uint16, numCipherSuites)
|
||||
for i := 0; i < numCipherSuites; i++ {
|
||||
m.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
|
||||
if m.cipherSuites[i] == scsvRenegotiation {
|
||||
m.secureRenegotiationSupported = true
|
||||
}
|
||||
}
|
||||
data = data[2+cipherSuiteLen:]
|
||||
if len(data) < 1 {
|
||||
return false
|
||||
}
|
||||
compressionMethodsLen := int(data[0])
|
||||
if len(data) < 1+compressionMethodsLen {
|
||||
return false
|
||||
}
|
||||
m.compressionMethods = data[1 : 1+compressionMethodsLen]
|
||||
data = data[1+compressionMethodsLen:]
|
||||
|
||||
m.nextProtoNeg = false
|
||||
m.serverName = ""
|
||||
m.ocspStapling = false
|
||||
m.ticketSupported = false
|
||||
m.sessionTicket = nil
|
||||
m.supportedSignatureAlgorithms = nil
|
||||
m.alpnProtocols = nil
|
||||
m.scts = false
|
||||
|
||||
if len(data) == 0 {
|
||||
// ClientHello is optionally followed by extension data
|
||||
return true
|
||||
}
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
extensionsLength := int(data[0])<<8 | int(data[1])
|
||||
data = data[2:]
|
||||
if extensionsLength != len(data) {
|
||||
return false
|
||||
}
|
||||
|
||||
for len(data) != 0 {
|
||||
if len(data) < 4 {
|
||||
return false
|
||||
}
|
||||
extension := uint16(data[0])<<8 | uint16(data[1])
|
||||
length := int(data[2])<<8 | int(data[3])
|
||||
data = data[4:]
|
||||
if len(data) < length {
|
||||
return false
|
||||
}
|
||||
|
||||
switch extension {
|
||||
case extensionServerName:
|
||||
d := data[:length]
|
||||
if len(d) < 2 {
|
||||
return false
|
||||
}
|
||||
namesLen := int(d[0])<<8 | int(d[1])
|
||||
d = d[2:]
|
||||
if len(d) != namesLen {
|
||||
return false
|
||||
}
|
||||
for len(d) > 0 {
|
||||
if len(d) < 3 {
|
||||
return false
|
||||
}
|
||||
nameType := d[0]
|
||||
nameLen := int(d[1])<<8 | int(d[2])
|
||||
d = d[3:]
|
||||
if len(d) < nameLen {
|
||||
return false
|
||||
}
|
||||
if nameType == 0 {
|
||||
m.serverName = string(d[:nameLen])
|
||||
// An SNI value may not include a
|
||||
// trailing dot. See
|
||||
// https://tools.ietf.org/html/rfc6066#section-3.
|
||||
if strings.HasSuffix(m.serverName, ".") {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
d = d[nameLen:]
|
||||
}
|
||||
case extensionNextProtoNeg:
|
||||
if length > 0 {
|
||||
return false
|
||||
}
|
||||
m.nextProtoNeg = true
|
||||
case extensionStatusRequest:
|
||||
m.ocspStapling = length > 0 && data[0] == statusTypeOCSP
|
||||
case extensionSupportedCurves:
|
||||
// https://tools.ietf.org/html/rfc4492#section-5.5.1
|
||||
if length < 2 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])<<8 | int(data[1])
|
||||
if l%2 == 1 || length != l+2 {
|
||||
return false
|
||||
}
|
||||
numCurves := l / 2
|
||||
m.supportedCurves = make([]CurveID, numCurves)
|
||||
d := data[2:]
|
||||
for i := 0; i < numCurves; i++ {
|
||||
m.supportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1])
|
||||
d = d[2:]
|
||||
}
|
||||
case extensionSupportedPoints:
|
||||
// https://tools.ietf.org/html/rfc4492#section-5.5.2
|
||||
if length < 1 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])
|
||||
if length != l+1 {
|
||||
return false
|
||||
}
|
||||
m.supportedPoints = make([]uint8, l)
|
||||
copy(m.supportedPoints, data[1:])
|
||||
case extensionSessionTicket:
|
||||
// https://tools.ietf.org/html/rfc5077#section-3.2
|
||||
m.ticketSupported = true
|
||||
m.sessionTicket = data[:length]
|
||||
case extensionSignatureAlgorithms:
|
||||
// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
|
||||
if length < 2 || length&1 != 0 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])<<8 | int(data[1])
|
||||
if l != length-2 {
|
||||
return false
|
||||
}
|
||||
n := l / 2
|
||||
d := data[2:]
|
||||
m.supportedSignatureAlgorithms = make([]SignatureScheme, n)
|
||||
for i := range m.supportedSignatureAlgorithms {
|
||||
m.supportedSignatureAlgorithms[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1])
|
||||
d = d[2:]
|
||||
}
|
||||
case extensionRenegotiationInfo:
|
||||
if length == 0 {
|
||||
return false
|
||||
}
|
||||
d := data[:length]
|
||||
l := int(d[0])
|
||||
d = d[1:]
|
||||
if l != len(d) {
|
||||
return false
|
||||
}
|
||||
|
||||
m.secureRenegotiation = d
|
||||
m.secureRenegotiationSupported = true
|
||||
case extensionALPN:
|
||||
if length < 2 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])<<8 | int(data[1])
|
||||
if l != length-2 {
|
||||
return false
|
||||
}
|
||||
d := data[2:length]
|
||||
for len(d) != 0 {
|
||||
stringLen := int(d[0])
|
||||
d = d[1:]
|
||||
if stringLen == 0 || stringLen > len(d) {
|
||||
return false
|
||||
}
|
||||
m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen]))
|
||||
d = d[stringLen:]
|
||||
}
|
||||
case extensionSCT:
|
||||
m.scts = true
|
||||
if length != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
data = data[length:]
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
//en
|
||||
func AesEncrypt(origData, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
origData = PKCS5Padding(origData, blockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||
crypted := make([]byte, len(origData))
|
||||
blockMode.CryptBlocks(crypted, origData)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
//de
|
||||
func AesDecrypt(crypted, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
origData := make([]byte, len(crypted))
|
||||
blockMode.CryptBlocks(origData, crypted)
|
||||
err, origData = PKCS5UnPadding(origData)
|
||||
return origData, err
|
||||
}
|
||||
|
||||
//Completion when the length is insufficient
|
||||
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
//Remove excess
|
||||
func PKCS5UnPadding(origData []byte) (error, []byte) {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
if (length - unpadding) < 0 {
|
||||
return errors.New("len error"), nil
|
||||
}
|
||||
return nil, origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
//Generate 32-bit MD5 strings
|
||||
func Md5(s string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
//Generating Random Verification Key
|
||||
func GetRandomString(l int) string {
|
||||
str := "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
bytes := []byte(str)
|
||||
result := []byte{}
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < l; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return string(result)
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
var (
|
||||
cert tls.Certificate
|
||||
)
|
||||
|
||||
func InitTls() {
|
||||
c, k, err := generateKeyPair("NPS Org")
|
||||
if err == nil {
|
||||
cert, err = tls.X509KeyPair(c, k)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln("Error initializing crypto certs", err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewTlsServerConn(conn net.Conn) net.Conn {
|
||||
var err error
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
return tls.Server(conn, config)
|
||||
}
|
||||
|
||||
func NewTlsClientConn(conn net.Conn) net.Conn {
|
||||
conf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
return tls.Client(conn, conf)
|
||||
}
|
||||
|
||||
func generateKeyPair(CommonName string) (rawCert, rawKey []byte, err error) {
|
||||
// Create private key and self-signed certificate
|
||||
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
validFor := time.Hour * 24 * 365 * 10 // ten years
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(validFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"My Company Name LTD."},
|
||||
CommonName: CommonName,
|
||||
Country: []string{"US"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
|
||||
return
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
)
|
||||
|
||||
func InitDaemon(f string, runPath string, pidPath string) {
|
||||
if len(os.Args) < 2 {
|
||||
return
|
||||
}
|
||||
var args []string
|
||||
args = append(args, os.Args[0])
|
||||
if len(os.Args) >= 2 {
|
||||
args = append(args, os.Args[2:]...)
|
||||
}
|
||||
args = append(args, "-log=file")
|
||||
switch os.Args[1] {
|
||||
case "start":
|
||||
start(args, f, pidPath, runPath)
|
||||
os.Exit(0)
|
||||
case "stop":
|
||||
stop(f, args[0], pidPath)
|
||||
os.Exit(0)
|
||||
case "restart":
|
||||
stop(f, args[0], pidPath)
|
||||
start(args, f, pidPath, runPath)
|
||||
os.Exit(0)
|
||||
case "status":
|
||||
if status(f, pidPath) {
|
||||
log.Printf("%s is running", f)
|
||||
} else {
|
||||
log.Printf("%s is not running", f)
|
||||
}
|
||||
os.Exit(0)
|
||||
case "reload":
|
||||
reload(f, pidPath)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func reload(f string, pidPath string) {
|
||||
if f == "nps" && !common.IsWindows() && !status(f, pidPath) {
|
||||
log.Println("reload fail")
|
||||
return
|
||||
}
|
||||
var c *exec.Cmd
|
||||
var err error
|
||||
b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid"))
|
||||
if err == nil {
|
||||
c = exec.Command("/bin/bash", "-c", `kill -30 `+string(b))
|
||||
} else {
|
||||
log.Fatalln("reload error,pid file does not exist")
|
||||
}
|
||||
if c.Run() == nil {
|
||||
log.Println("reload success")
|
||||
} else {
|
||||
log.Println("reload fail")
|
||||
}
|
||||
}
|
||||
|
||||
func status(f string, pidPath string) bool {
|
||||
var cmd *exec.Cmd
|
||||
b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid"))
|
||||
if err == nil {
|
||||
if !common.IsWindows() {
|
||||
cmd = exec.Command("/bin/sh", "-c", "ps -ax | awk '{ print $1 }' | grep "+string(b))
|
||||
} else {
|
||||
cmd = exec.Command("tasklist")
|
||||
}
|
||||
out, _ := cmd.Output()
|
||||
if strings.Index(string(out), string(b)) > -1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func start(osArgs []string, f string, pidPath, runPath string) {
|
||||
if status(f, pidPath) {
|
||||
log.Printf(" %s is running", f)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(osArgs[0], osArgs[1:]...)
|
||||
cmd.Start()
|
||||
if cmd.Process.Pid > 0 {
|
||||
log.Println("start ok , pid:", cmd.Process.Pid, "config path:", runPath)
|
||||
d1 := []byte(strconv.Itoa(cmd.Process.Pid))
|
||||
ioutil.WriteFile(filepath.Join(pidPath, f+".pid"), d1, 0600)
|
||||
} else {
|
||||
log.Println("start error")
|
||||
}
|
||||
}
|
||||
|
||||
func stop(f string, p string, pidPath string) {
|
||||
if !status(f, pidPath) {
|
||||
log.Printf(" %s is not running", f)
|
||||
return
|
||||
}
|
||||
var c *exec.Cmd
|
||||
var err error
|
||||
if common.IsWindows() {
|
||||
p := strings.Split(p, `\`)
|
||||
c = exec.Command("taskkill", "/F", "/IM", p[len(p)-1])
|
||||
} else {
|
||||
b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid"))
|
||||
if err == nil {
|
||||
c = exec.Command("/bin/bash", "-c", `kill -9 `+string(b))
|
||||
} else {
|
||||
log.Fatalln("stop error,pid file does not exist")
|
||||
}
|
||||
}
|
||||
err = c.Run()
|
||||
if err != nil {
|
||||
log.Println("stop error,", err)
|
||||
} else {
|
||||
log.Println("stop ok")
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func init() {
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, syscall.SIGUSR1)
|
||||
go func() {
|
||||
for {
|
||||
<-s
|
||||
beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf"))
|
||||
}
|
||||
}()
|
||||
}
|
361
lib/file/db.go
@ -1,361 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/rate"
|
||||
)
|
||||
|
||||
type DbUtils struct {
|
||||
JsonDb *JsonDb
|
||||
}
|
||||
|
||||
var (
|
||||
Db *DbUtils
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
//init csv from file
|
||||
func GetDb() *DbUtils {
|
||||
once.Do(func() {
|
||||
jsonDb := NewJsonDb(common.GetRunPath())
|
||||
jsonDb.LoadClientFromJsonFile()
|
||||
jsonDb.LoadTaskFromJsonFile()
|
||||
jsonDb.LoadHostFromJsonFile()
|
||||
Db = &DbUtils{JsonDb: jsonDb}
|
||||
})
|
||||
return Db
|
||||
}
|
||||
|
||||
func GetMapKeys(m sync.Map, isSort bool, sortKey, order string) (keys []int) {
|
||||
if sortKey != "" && isSort {
|
||||
return sortClientByKey(m, sortKey, order)
|
||||
}
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
keys = append(keys, key.(int))
|
||||
return true
|
||||
})
|
||||
sort.Ints(keys)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetClientList(start, length int, search, sort, order string, clientId int) ([]*Client, int) {
|
||||
list := make([]*Client, 0)
|
||||
var cnt int
|
||||
keys := GetMapKeys(s.JsonDb.Clients, true, sort, order)
|
||||
for _, key := range keys {
|
||||
if value, ok := s.JsonDb.Clients.Load(key); ok {
|
||||
v := value.(*Client)
|
||||
if v.NoDisplay {
|
||||
continue
|
||||
}
|
||||
if clientId != 0 && clientId != v.Id {
|
||||
continue
|
||||
}
|
||||
if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.VerifyKey, search) || strings.Contains(v.Remark, search)) {
|
||||
continue
|
||||
}
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length >= 0 {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, cnt
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetIdByVerifyKey(vKey string, addr string) (id int, err error) {
|
||||
var exist bool
|
||||
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Client)
|
||||
if common.Getverifyval(v.VerifyKey) == vKey && v.Status {
|
||||
v.Addr = common.GetIpByAddr(addr)
|
||||
id = v.Id
|
||||
exist = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if exist {
|
||||
return
|
||||
}
|
||||
return 0, errors.New("not found")
|
||||
}
|
||||
|
||||
func (s *DbUtils) NewTask(t *Tunnel) (err error) {
|
||||
s.JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Tunnel)
|
||||
if (v.Mode == "secret" || v.Mode == "p2p") && v.Password == t.Password {
|
||||
err = errors.New(fmt.Sprintf("secret mode keys %s must be unique", t.Password))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.Flow = new(Flow)
|
||||
s.JsonDb.Tasks.Store(t.Id, t)
|
||||
s.JsonDb.StoreTasksToJsonFile()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DbUtils) UpdateTask(t *Tunnel) error {
|
||||
s.JsonDb.Tasks.Store(t.Id, t)
|
||||
s.JsonDb.StoreTasksToJsonFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DbUtils) DelTask(id int) error {
|
||||
s.JsonDb.Tasks.Delete(id)
|
||||
s.JsonDb.StoreTasksToJsonFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
//md5 password
|
||||
func (s *DbUtils) GetTaskByMd5Password(p string) (t *Tunnel) {
|
||||
s.JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
if crypt.Md5(value.(*Tunnel).Password) == p {
|
||||
t = value.(*Tunnel)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetTask(id int) (t *Tunnel, err error) {
|
||||
if v, ok := s.JsonDb.Tasks.Load(id); ok {
|
||||
t = v.(*Tunnel)
|
||||
return
|
||||
}
|
||||
err = errors.New("not found")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DbUtils) DelHost(id int) error {
|
||||
s.JsonDb.Hosts.Delete(id)
|
||||
s.JsonDb.StoreHostToJsonFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DbUtils) IsHostExist(h *Host) bool {
|
||||
var exist bool
|
||||
s.JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Host)
|
||||
if v.Id != h.Id && v.Host == h.Host && h.Location == v.Location && (v.Scheme == "all" || v.Scheme == h.Scheme) {
|
||||
exist = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return exist
|
||||
}
|
||||
|
||||
func (s *DbUtils) NewHost(t *Host) error {
|
||||
if t.Location == "" {
|
||||
t.Location = "/"
|
||||
}
|
||||
if s.IsHostExist(t) {
|
||||
return errors.New("host has exist")
|
||||
}
|
||||
t.Flow = new(Flow)
|
||||
s.JsonDb.Hosts.Store(t.Id, t)
|
||||
s.JsonDb.StoreHostToJsonFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetHost(start, length int, id int, search string) ([]*Host, int) {
|
||||
list := make([]*Host, 0)
|
||||
var cnt int
|
||||
keys := GetMapKeys(s.JsonDb.Hosts, false, "", "")
|
||||
for _, key := range keys {
|
||||
if value, ok := s.JsonDb.Hosts.Load(key); ok {
|
||||
v := value.(*Host)
|
||||
if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.Host, search) || strings.Contains(v.Remark, search)) {
|
||||
continue
|
||||
}
|
||||
if id == 0 || v.Client.Id == id {
|
||||
cnt++
|
||||
if start--; start < 0 {
|
||||
if length--; length >= 0 {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list, cnt
|
||||
}
|
||||
|
||||
func (s *DbUtils) DelClient(id int) error {
|
||||
s.JsonDb.Clients.Delete(id)
|
||||
s.JsonDb.StoreClientsToJsonFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DbUtils) NewClient(c *Client) error {
|
||||
var isNotSet bool
|
||||
if c.WebUserName != "" && !s.VerifyUserName(c.WebUserName, c.Id) {
|
||||
return errors.New("web login username duplicate, please reset")
|
||||
}
|
||||
reset:
|
||||
if c.VerifyKey == "" || isNotSet {
|
||||
isNotSet = true
|
||||
c.VerifyKey = crypt.GetRandomString(16)
|
||||
}
|
||||
if c.RateLimit == 0 {
|
||||
c.Rate = rate.NewRate(int64(2 << 23))
|
||||
} else if c.Rate == nil {
|
||||
c.Rate = rate.NewRate(int64(c.RateLimit * 1024))
|
||||
}
|
||||
c.Rate.Start()
|
||||
if !s.VerifyVkey(c.VerifyKey, c.Id) {
|
||||
if isNotSet {
|
||||
goto reset
|
||||
}
|
||||
return errors.New("Vkey duplicate, please reset")
|
||||
}
|
||||
if c.Id == 0 {
|
||||
c.Id = int(s.JsonDb.GetClientId())
|
||||
}
|
||||
if c.Flow == nil {
|
||||
c.Flow = new(Flow)
|
||||
}
|
||||
s.JsonDb.Clients.Store(c.Id, c)
|
||||
s.JsonDb.StoreClientsToJsonFile()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DbUtils) VerifyVkey(vkey string, id int) (res bool) {
|
||||
res = true
|
||||
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Client)
|
||||
if v.VerifyKey == vkey && v.Id != id {
|
||||
res = false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *DbUtils) VerifyUserName(username string, id int) (res bool) {
|
||||
res = true
|
||||
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Client)
|
||||
if v.WebUserName == username && v.Id != id {
|
||||
res = false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *DbUtils) UpdateClient(t *Client) error {
|
||||
s.JsonDb.Clients.Store(t.Id, t)
|
||||
if t.RateLimit == 0 {
|
||||
t.Rate = rate.NewRate(int64(2 << 23))
|
||||
t.Rate.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DbUtils) IsPubClient(id int) bool {
|
||||
client, err := s.GetClient(id)
|
||||
if err == nil {
|
||||
return client.NoDisplay
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetClient(id int) (c *Client, err error) {
|
||||
if v, ok := s.JsonDb.Clients.Load(id); ok {
|
||||
c = v.(*Client)
|
||||
return
|
||||
}
|
||||
err = errors.New("未找到客户端")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetClientIdByVkey(vkey string) (id int, err error) {
|
||||
var exist bool
|
||||
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Client)
|
||||
if crypt.Md5(v.VerifyKey) == vkey {
|
||||
exist = true
|
||||
id = v.Id
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if exist {
|
||||
return
|
||||
}
|
||||
err = errors.New("未找到客户端")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DbUtils) GetHostById(id int) (h *Host, err error) {
|
||||
if v, ok := s.JsonDb.Hosts.Load(id); ok {
|
||||
h = v.(*Host)
|
||||
return
|
||||
}
|
||||
err = errors.New("The host could not be parsed")
|
||||
return
|
||||
}
|
||||
|
||||
//get key by host from x
|
||||
func (s *DbUtils) GetInfoByHost(host string, r *http.Request) (h *Host, err error) {
|
||||
var hosts []*Host
|
||||
//Handling Ported Access
|
||||
host = common.GetIpByAddr(host)
|
||||
s.JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Host)
|
||||
if v.IsClose {
|
||||
return true
|
||||
}
|
||||
//Remove http(s) http(s)://a.proxy.com
|
||||
//*.proxy.com *.a.proxy.com Do some pan-parsing
|
||||
if v.Scheme != "all" && v.Scheme != r.URL.Scheme {
|
||||
return true
|
||||
}
|
||||
tmpHost := v.Host
|
||||
if strings.Contains(tmpHost, "*") {
|
||||
tmpHost = strings.Replace(tmpHost, "*", "", -1)
|
||||
if strings.Contains(host, tmpHost) {
|
||||
hosts = append(hosts, v)
|
||||
}
|
||||
} else if v.Host == host {
|
||||
hosts = append(hosts, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
for _, v := range hosts {
|
||||
//If not set, default matches all
|
||||
if v.Location == "" {
|
||||
v.Location = "/"
|
||||
}
|
||||
if strings.Index(r.RequestURI, v.Location) == 0 {
|
||||
if h == nil || (len(v.Location) > len(h.Location)) {
|
||||
h = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if h != nil {
|
||||
return
|
||||
}
|
||||
err = errors.New("The host could not be parsed")
|
||||
return
|
||||
}
|
201
lib/file/file.go
@ -1,201 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/rate"
|
||||
)
|
||||
|
||||
func NewJsonDb(runPath string) *JsonDb {
|
||||
return &JsonDb{
|
||||
RunPath: runPath,
|
||||
TaskFilePath: filepath.Join(runPath, "conf", "tasks.json"),
|
||||
HostFilePath: filepath.Join(runPath, "conf", "hosts.json"),
|
||||
ClientFilePath: filepath.Join(runPath, "conf", "clients.json"),
|
||||
}
|
||||
}
|
||||
|
||||
type JsonDb struct {
|
||||
Tasks sync.Map
|
||||
Hosts sync.Map
|
||||
HostsTmp sync.Map
|
||||
Clients sync.Map
|
||||
RunPath string
|
||||
ClientIncreaseId int32 //client increased id
|
||||
TaskIncreaseId int32 //task increased id
|
||||
HostIncreaseId int32 //host increased id
|
||||
TaskFilePath string //task file path
|
||||
HostFilePath string //host file path
|
||||
ClientFilePath string //client file path
|
||||
}
|
||||
|
||||
func (s *JsonDb) LoadTaskFromJsonFile() {
|
||||
loadSyncMapFromFile(s.TaskFilePath, func(v string) {
|
||||
var err error
|
||||
post := new(Tunnel)
|
||||
if json.Unmarshal([]byte(v), &post) != nil {
|
||||
return
|
||||
}
|
||||
if post.Client, err = s.GetClient(post.Client.Id); err != nil {
|
||||
return
|
||||
}
|
||||
s.Tasks.Store(post.Id, post)
|
||||
if post.Id > int(s.TaskIncreaseId) {
|
||||
s.TaskIncreaseId = int32(post.Id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *JsonDb) LoadClientFromJsonFile() {
|
||||
loadSyncMapFromFile(s.ClientFilePath, func(v string) {
|
||||
post := new(Client)
|
||||
if json.Unmarshal([]byte(v), &post) != nil {
|
||||
return
|
||||
}
|
||||
if post.RateLimit > 0 {
|
||||
post.Rate = rate.NewRate(int64(post.RateLimit * 1024))
|
||||
} else {
|
||||
post.Rate = rate.NewRate(int64(2 << 23))
|
||||
}
|
||||
post.Rate.Start()
|
||||
post.NowConn = 0
|
||||
s.Clients.Store(post.Id, post)
|
||||
if post.Id > int(s.ClientIncreaseId) {
|
||||
s.ClientIncreaseId = int32(post.Id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *JsonDb) LoadHostFromJsonFile() {
|
||||
loadSyncMapFromFile(s.HostFilePath, func(v string) {
|
||||
var err error
|
||||
post := new(Host)
|
||||
if json.Unmarshal([]byte(v), &post) != nil {
|
||||
return
|
||||
}
|
||||
if post.Client, err = s.GetClient(post.Client.Id); err != nil {
|
||||
return
|
||||
}
|
||||
s.Hosts.Store(post.Id, post)
|
||||
if post.Id > int(s.HostIncreaseId) {
|
||||
s.HostIncreaseId = int32(post.Id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *JsonDb) GetClient(id int) (c *Client, err error) {
|
||||
if v, ok := s.Clients.Load(id); ok {
|
||||
c = v.(*Client)
|
||||
return
|
||||
}
|
||||
err = errors.New("未找到客户端")
|
||||
return
|
||||
}
|
||||
|
||||
var hostLock sync.Mutex
|
||||
|
||||
func (s *JsonDb) StoreHostToJsonFile() {
|
||||
hostLock.Lock()
|
||||
storeSyncMapToFile(s.Hosts, s.HostFilePath)
|
||||
hostLock.Unlock()
|
||||
}
|
||||
|
||||
var taskLock sync.Mutex
|
||||
|
||||
func (s *JsonDb) StoreTasksToJsonFile() {
|
||||
taskLock.Lock()
|
||||
storeSyncMapToFile(s.Tasks, s.TaskFilePath)
|
||||
taskLock.Unlock()
|
||||
}
|
||||
|
||||
var clientLock sync.Mutex
|
||||
|
||||
func (s *JsonDb) StoreClientsToJsonFile() {
|
||||
clientLock.Lock()
|
||||
storeSyncMapToFile(s.Clients, s.ClientFilePath)
|
||||
clientLock.Unlock()
|
||||
}
|
||||
|
||||
func (s *JsonDb) GetClientId() int32 {
|
||||
return atomic.AddInt32(&s.ClientIncreaseId, 1)
|
||||
}
|
||||
|
||||
func (s *JsonDb) GetTaskId() int32 {
|
||||
return atomic.AddInt32(&s.TaskIncreaseId, 1)
|
||||
}
|
||||
|
||||
func (s *JsonDb) GetHostId() int32 {
|
||||
return atomic.AddInt32(&s.HostIncreaseId, 1)
|
||||
}
|
||||
|
||||
func loadSyncMapFromFile(filePath string, f func(value string)) {
|
||||
b, err := common.ReadAllFromFile(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range strings.Split(string(b), "\n"+common.CONN_DATA_SEQ) {
|
||||
f(v)
|
||||
}
|
||||
}
|
||||
|
||||
func storeSyncMapToFile(m sync.Map, filePath string) {
|
||||
file, err := os.Create(filePath + ".tmp")
|
||||
// first create a temporary file to store
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
var b []byte
|
||||
var err error
|
||||
switch value.(type) {
|
||||
case *Tunnel:
|
||||
obj := value.(*Tunnel)
|
||||
if obj.NoStore {
|
||||
return true
|
||||
}
|
||||
b, err = json.Marshal(obj)
|
||||
case *Host:
|
||||
obj := value.(*Host)
|
||||
if obj.NoStore {
|
||||
return true
|
||||
}
|
||||
b, err = json.Marshal(obj)
|
||||
case *Client:
|
||||
obj := value.(*Client)
|
||||
if obj.NoStore {
|
||||
return true
|
||||
}
|
||||
b, err = json.Marshal(obj)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
_, err = file.Write(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = file.Write([]byte("\n" + common.CONN_DATA_SEQ))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
_ = file.Sync()
|
||||
_ = file.Close()
|
||||
// must close file first, then rename it
|
||||
err = os.Rename(filePath+".tmp", filePath)
|
||||
if err != nil {
|
||||
logs.Error(err, "store to file err, data will lost")
|
||||
}
|
||||
// replace the file, maybe provides atomic operation
|
||||
}
|
210
lib/file/obj.go
@ -1,210 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/rate"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Flow struct {
|
||||
ExportFlow int64
|
||||
InletFlow int64
|
||||
FlowLimit int64
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *Flow) Add(in, out int64) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.InletFlow += int64(in)
|
||||
s.ExportFlow += int64(out)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
U string
|
||||
P string
|
||||
Compress bool
|
||||
Crypt bool
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Cnf *Config
|
||||
Id int //id
|
||||
VerifyKey string //verify key
|
||||
Addr string //the ip of client
|
||||
Remark string //remark
|
||||
Status bool //is allow connect
|
||||
IsConnect bool //is the client connect
|
||||
RateLimit int //rate /kb
|
||||
Flow *Flow //flow setting
|
||||
Rate *rate.Rate //rate limit
|
||||
NoStore bool //no store to file
|
||||
NoDisplay bool //no display on web
|
||||
MaxConn int //the max connection num of client allow
|
||||
NowConn int32 //the connection num of now
|
||||
WebUserName string //the username of web login
|
||||
WebPassword string //the password of web login
|
||||
ConfigConnAllow bool //is allow connected by config file
|
||||
MaxTunnelNum int
|
||||
Version string
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewClient(vKey string, noStore bool, noDisplay bool) *Client {
|
||||
return &Client{
|
||||
Cnf: new(Config),
|
||||
Id: 0,
|
||||
VerifyKey: vKey,
|
||||
Addr: "",
|
||||
Remark: "",
|
||||
Status: true,
|
||||
IsConnect: false,
|
||||
RateLimit: 0,
|
||||
Flow: new(Flow),
|
||||
Rate: nil,
|
||||
NoStore: noStore,
|
||||
RWMutex: sync.RWMutex{},
|
||||
NoDisplay: noDisplay,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Client) CutConn() {
|
||||
atomic.AddInt32(&s.NowConn, 1)
|
||||
}
|
||||
|
||||
func (s *Client) AddConn() {
|
||||
atomic.AddInt32(&s.NowConn, -1)
|
||||
}
|
||||
|
||||
func (s *Client) GetConn() bool {
|
||||
if s.MaxConn == 0 || int(s.NowConn) < s.MaxConn {
|
||||
s.CutConn()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Client) HasTunnel(t *Tunnel) (exist bool) {
|
||||
GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Tunnel)
|
||||
if v.Client.Id == s.Id && v.Port == t.Port && t.Port != 0 {
|
||||
exist = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Client) GetTunnelNum() (num int) {
|
||||
GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Tunnel)
|
||||
if v.Client.Id == s.Id {
|
||||
num++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Client) HasHost(h *Host) bool {
|
||||
var has bool
|
||||
GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*Host)
|
||||
if v.Client.Id == s.Id && v.Host == h.Host && h.Location == v.Location {
|
||||
has = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return has
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
Id int
|
||||
Port int
|
||||
ServerIp string
|
||||
Mode string
|
||||
Status bool
|
||||
RunStatus bool
|
||||
Client *Client
|
||||
Ports string
|
||||
Flow *Flow
|
||||
Password string
|
||||
Remark string
|
||||
TargetAddr string
|
||||
NoStore bool
|
||||
LocalPath string
|
||||
StripPre string
|
||||
Target *Target
|
||||
MultiAccount *MultiAccount
|
||||
Health
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type Health struct {
|
||||
HealthCheckTimeout int
|
||||
HealthMaxFail int
|
||||
HealthCheckInterval int
|
||||
HealthNextTime time.Time
|
||||
HealthMap map[string]int
|
||||
HttpHealthUrl string
|
||||
HealthRemoveArr []string
|
||||
HealthCheckType string
|
||||
HealthCheckTarget string
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
Id int
|
||||
Host string //host
|
||||
HeaderChange string //header change
|
||||
HostChange string //host change
|
||||
Location string //url router
|
||||
Remark string //remark
|
||||
Scheme string //http https all
|
||||
CertFilePath string
|
||||
KeyFilePath string
|
||||
NoStore bool
|
||||
IsClose bool
|
||||
Flow *Flow
|
||||
Client *Client
|
||||
Target *Target //目标
|
||||
Health `json:"-"`
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
nowIndex int
|
||||
TargetStr string
|
||||
TargetArr []string
|
||||
LocalProxy bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type MultiAccount struct {
|
||||
AccountMap map[string]string // multi account and pwd
|
||||
}
|
||||
|
||||
func (s *Target) GetRandomTarget() (string, error) {
|
||||
if s.TargetArr == nil {
|
||||
s.TargetArr = strings.Split(s.TargetStr, "\n")
|
||||
}
|
||||
if len(s.TargetArr) == 1 {
|
||||
return s.TargetArr[0], nil
|
||||
}
|
||||
if len(s.TargetArr) == 0 {
|
||||
return "", errors.New("all inward-bending targets are offline")
|
||||
}
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.nowIndex >= len(s.TargetArr)-1 {
|
||||
s.nowIndex = -1
|
||||
}
|
||||
s.nowIndex++
|
||||
return s.TargetArr[s.nowIndex], nil
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A data structure to hold a key/value pair.
|
||||
type Pair struct {
|
||||
key string //sort key
|
||||
cId int
|
||||
order string
|
||||
clientFlow *Flow
|
||||
}
|
||||
|
||||
// A slice of Pairs that implements sort.Interface to sort by Value.
|
||||
type PairList []*Pair
|
||||
|
||||
func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p PairList) Len() int { return len(p) }
|
||||
func (p PairList) Less(i, j int) bool {
|
||||
if p[i].order == "desc" {
|
||||
return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() < reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()
|
||||
}
|
||||
return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() > reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()
|
||||
}
|
||||
|
||||
// A function to turn a map into a PairList, then sort and return it.
|
||||
func sortClientByKey(m sync.Map, sortKey, order string) (res []int) {
|
||||
p := make(PairList, 0)
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
p = append(p, &Pair{sortKey, value.(*Client).Id, order, value.(*Client).Flow})
|
||||
return true
|
||||
})
|
||||
sort.Sort(p)
|
||||
for _, v := range p {
|
||||
res = append(res, v.cId)
|
||||
}
|
||||
return
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package goroutine
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/file"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type connGroup struct {
|
||||
src io.ReadWriteCloser
|
||||
dst io.ReadWriteCloser
|
||||
wg *sync.WaitGroup
|
||||
n *int64
|
||||
}
|
||||
|
||||
func newConnGroup(dst, src io.ReadWriteCloser, wg *sync.WaitGroup, n *int64) connGroup {
|
||||
return connGroup{
|
||||
src: src,
|
||||
dst: dst,
|
||||
wg: wg,
|
||||
n: n,
|
||||
}
|
||||
}
|
||||
|
||||
func copyConnGroup(group interface{}) {
|
||||
cg, ok := group.(connGroup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
*cg.n, err = common.CopyBuffer(cg.dst, cg.src)
|
||||
if err != nil {
|
||||
cg.src.Close()
|
||||
cg.dst.Close()
|
||||
//logs.Warn("close npc by copy from nps", err, c.connId)
|
||||
}
|
||||
cg.wg.Done()
|
||||
}
|
||||
|
||||
type Conns struct {
|
||||
conn1 io.ReadWriteCloser // mux connection
|
||||
conn2 net.Conn // outside connection
|
||||
flow *file.Flow
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow, wg *sync.WaitGroup) Conns {
|
||||
return Conns{
|
||||
conn1: c1,
|
||||
conn2: c2,
|
||||
flow: flow,
|
||||
wg: wg,
|
||||
}
|
||||
}
|
||||
|
||||
func copyConns(group interface{}) {
|
||||
conns := group.(Conns)
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(2)
|
||||
var in, out int64
|
||||
_ = connCopyPool.Invoke(newConnGroup(conns.conn1, conns.conn2, wg, &in))
|
||||
// outside to mux : incoming
|
||||
_ = connCopyPool.Invoke(newConnGroup(conns.conn2, conns.conn1, wg, &out))
|
||||
// mux to outside : outgoing
|
||||
wg.Wait()
|
||||
if conns.flow != nil {
|
||||
conns.flow.Add(in, out)
|
||||
}
|
||||
conns.wg.Done()
|
||||
}
|
||||
|
||||
var connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false))
|
||||
var CopyConnsPool, _ = ants.NewPoolWithFunc(100000, copyConns, ants.WithNonblocking(false))
|
@ -1,363 +0,0 @@
|
||||
package install
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/common"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/c4milo/unpackit"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Keep it in sync with the template from service_sysv_linux.go file
|
||||
// Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt
|
||||
const SysvScript = `#!/bin/sh
|
||||
# For RedHat and cousins:
|
||||
# chkconfig: - 99 01
|
||||
# description: {{.Description}}
|
||||
# processname: {{.Path}}
|
||||
### BEGIN INIT INFO
|
||||
# Provides: {{.Path}}
|
||||
# Required-Start:
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: {{.DisplayName}}
|
||||
# Description: {{.Description}}
|
||||
### END INIT INFO
|
||||
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
|
||||
name=$(basename $(readlink -f $0))
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1
|
||||
}
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
{{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
|
||||
$cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill $(get_pid)
|
||||
for i in $(seq 1 10)
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
`
|
||||
|
||||
const SystemdScript = `[Unit]
|
||||
Description={{.Description}}
|
||||
ConditionFileIsExecutable={{.Path|cmdEscape}}
|
||||
{{range $i, $dep := .Dependencies}}
|
||||
{{$dep}} {{end}}
|
||||
[Service]
|
||||
LimitNOFILE=65536
|
||||
StartLimitInterval=5
|
||||
StartLimitBurst=10
|
||||
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
||||
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
|
||||
{{if .UserName}}User={{.UserName}}{{end}}
|
||||
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
|
||||
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
|
||||
{{if and .LogOutput .HasOutputFileSupport -}}
|
||||
StandardOutput=file:/var/log/{{.Name}}.out
|
||||
StandardError=file:/var/log/{{.Name}}.err
|
||||
{{- end}}
|
||||
Restart=always
|
||||
RestartSec=120
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`
|
||||
|
||||
func UpdateNps() {
|
||||
destPath := downloadLatest("server")
|
||||
//复制文件到对应目录
|
||||
copyStaticFile(destPath, "nps")
|
||||
fmt.Println("Update completed, please restart")
|
||||
}
|
||||
|
||||
func UpdateNpc() {
|
||||
destPath := downloadLatest("client")
|
||||
//复制文件到对应目录
|
||||
copyStaticFile(destPath, "npc")
|
||||
fmt.Println("Update completed, please restart")
|
||||
}
|
||||
|
||||
type release struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
func downloadLatest(bin string) string {
|
||||
// get version
|
||||
data, err := http.Get("https://api.github.com/repos/ehang-io/nps/releases/latest")
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
b, err := ioutil.ReadAll(data.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rl := new(release)
|
||||
json.Unmarshal(b, &rl)
|
||||
version := rl.TagName
|
||||
fmt.Println("the latest version is", version)
|
||||
filename := runtime.GOOS + "_" + runtime.GOARCH + "_" + bin + ".tar.gz"
|
||||
// download latest package
|
||||
downloadUrl := fmt.Sprintf("https://ehang.io/nps/releases/download/%s/%s", version, filename)
|
||||
fmt.Println("download package from ", downloadUrl)
|
||||
resp, err := http.Get(downloadUrl)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
destPath, err := unpackit.Unpack(resp.Body, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if bin == "server" {
|
||||
destPath = strings.Replace(destPath, "/web", "", -1)
|
||||
destPath = strings.Replace(destPath, `\web`, "", -1)
|
||||
destPath = strings.Replace(destPath, "/views", "", -1)
|
||||
destPath = strings.Replace(destPath, `\views`, "", -1)
|
||||
} else {
|
||||
destPath = strings.Replace(destPath, `\conf`, "", -1)
|
||||
destPath = strings.Replace(destPath, "/conf", "", -1)
|
||||
}
|
||||
return destPath
|
||||
}
|
||||
|
||||
func copyStaticFile(srcPath, bin string) string {
|
||||
path := common.GetInstallPath()
|
||||
if bin == "nps" {
|
||||
//复制文件到对应目录
|
||||
if err := CopyDir(filepath.Join(srcPath, "web", "views"), filepath.Join(path, "web", "views")); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
chMod(filepath.Join(path, "web", "views"), 0766)
|
||||
if err := CopyDir(filepath.Join(srcPath, "web", "static"), filepath.Join(path, "web", "static")); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
chMod(filepath.Join(path, "web", "static"), 0766)
|
||||
}
|
||||
binPath, _ := filepath.Abs(os.Args[0])
|
||||
if !common.IsWindows() {
|
||||
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin); err != nil {
|
||||
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin); err != nil {
|
||||
log.Fatalln(err)
|
||||
} else {
|
||||
copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin+"-update")
|
||||
chMod("/usr/local/bin/"+bin+"-update", 0755)
|
||||
binPath = "/usr/local/bin/" + bin
|
||||
}
|
||||
} else {
|
||||
copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin+"-update")
|
||||
chMod("/usr/bin/"+bin+"-update", 0755)
|
||||
binPath = "/usr/bin/" + bin
|
||||
}
|
||||
} else {
|
||||
copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+"-update.exe"))
|
||||
copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+".exe"))
|
||||
}
|
||||
chMod(binPath, 0755)
|
||||
return binPath
|
||||
}
|
||||
|
||||
func InstallNpc() {
|
||||
path := common.GetInstallPath()
|
||||
if !common.FileExists(path) {
|
||||
err := os.Mkdir(path, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
copyStaticFile(common.GetAppPath(), "npc")
|
||||
}
|
||||
|
||||
func InstallNps() string {
|
||||
path := common.GetInstallPath()
|
||||
if common.FileExists(path) {
|
||||
MkidrDirAll(path, "web/static", "web/views")
|
||||
} else {
|
||||
MkidrDirAll(path, "conf", "web/static", "web/views")
|
||||
// not copy config if the config file is exist
|
||||
if err := CopyDir(filepath.Join(common.GetAppPath(), "conf"), filepath.Join(path, "conf")); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
chMod(filepath.Join(path, "conf"), 0766)
|
||||
}
|
||||
binPath := copyStaticFile(common.GetAppPath(), "nps")
|
||||
log.Println("install ok!")
|
||||
log.Println("Static files and configuration files in the current directory will be useless")
|
||||
log.Println("The new configuration file is located in", path, "you can edit them")
|
||||
if !common.IsWindows() {
|
||||
log.Println(`You can start with:
|
||||
nps start|stop|restart|uninstall|update or nps-update update
|
||||
anywhere!`)
|
||||
} else {
|
||||
log.Println(`You can copy executable files to any directory and start working with:
|
||||
nps.exe start|stop|restart|uninstall|update or nps-update.exe update
|
||||
now!`)
|
||||
}
|
||||
chMod(common.GetLogPath(), 0777)
|
||||
return binPath
|
||||
}
|
||||
func MkidrDirAll(path string, v ...string) {
|
||||
for _, item := range v {
|
||||
if err := os.MkdirAll(filepath.Join(path, item), 0755); err != nil {
|
||||
log.Fatalf("Failed to create directory %s error:%s", path, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CopyDir(srcPath string, destPath string) error {
|
||||
//检测目录正确性
|
||||
if srcInfo, err := os.Stat(srcPath); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return err
|
||||
} else {
|
||||
if !srcInfo.IsDir() {
|
||||
e := errors.New("SrcPath is not the right directory!")
|
||||
return e
|
||||
}
|
||||
}
|
||||
if destInfo, err := os.Stat(destPath); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if !destInfo.IsDir() {
|
||||
e := errors.New("DestInfo is not the right directory!")
|
||||
return e
|
||||
}
|
||||
}
|
||||
err := filepath.Walk(srcPath, func(path string, f os.FileInfo, err error) error {
|
||||
if f == nil {
|
||||
return err
|
||||
}
|
||||
if !f.IsDir() {
|
||||
destNewPath := strings.Replace(path, srcPath, destPath, -1)
|
||||
log.Println("copy file ::" + path + " to " + destNewPath)
|
||||
copyFile(path, destNewPath)
|
||||
if !common.IsWindows() {
|
||||
chMod(destNewPath, 0766)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
//生成目录并拷贝文件
|
||||
func copyFile(src, dest string) (w int64, err error) {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer srcFile.Close()
|
||||
//分割path目录
|
||||
destSplitPathDirs := strings.Split(dest, string(filepath.Separator))
|
||||
|
||||
//检测时候存在目录
|
||||
destSplitPath := ""
|
||||
for index, dir := range destSplitPathDirs {
|
||||
if index < len(destSplitPathDirs)-1 {
|
||||
destSplitPath = destSplitPath + dir + string(filepath.Separator)
|
||||
b, _ := pathExists(destSplitPath)
|
||||
if b == false {
|
||||
log.Println("mkdir:" + destSplitPath)
|
||||
//创建目录
|
||||
err := os.Mkdir(destSplitPath, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dstFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
return io.Copy(dstFile, srcFile)
|
||||
}
|
||||
|
||||
//检测文件夹路径时候存在
|
||||
func pathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func chMod(name string, mode os.FileMode) {
|
||||
if !common.IsWindows() {
|
||||
os.Chmod(name, mode)
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package pmux
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PortConn struct {
|
||||
Conn net.Conn
|
||||
rs []byte
|
||||
readMore bool
|
||||
start int
|
||||
}
|
||||
|
||||
func newPortConn(conn net.Conn, rs []byte, readMore bool) *PortConn {
|
||||
return &PortConn{
|
||||
Conn: conn,
|
||||
rs: rs,
|
||||
readMore: readMore,
|
||||
}
|
||||
}
|
||||
|
||||
func (pConn *PortConn) Read(b []byte) (n int, err error) {
|
||||
if len(b) < len(pConn.rs)-pConn.start {
|
||||
defer func() {
|
||||
pConn.start = pConn.start + len(b)
|
||||
}()
|
||||
return copy(b, pConn.rs), nil
|
||||
}
|
||||
if pConn.start < len(pConn.rs) {
|
||||
defer func() {
|
||||
pConn.start = len(pConn.rs)
|
||||
}()
|
||||
n = copy(b, pConn.rs[pConn.start:])
|
||||
if !pConn.readMore {
|
||||
return
|
||||
}
|
||||
}
|
||||
var n2 = 0
|
||||
n2, err = pConn.Conn.Read(b[n:])
|
||||
n = n + n2
|
||||
return
|
||||
}
|
||||
|
||||
func (pConn *PortConn) Write(b []byte) (n int, err error) {
|
||||
return pConn.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (pConn *PortConn) Close() error {
|
||||
return pConn.Conn.Close()
|
||||
}
|
||||
|
||||
func (pConn *PortConn) LocalAddr() net.Addr {
|
||||
return pConn.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (pConn *PortConn) RemoteAddr() net.Addr {
|
||||
return pConn.Conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (pConn *PortConn) SetDeadline(t time.Time) error {
|
||||
return pConn.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (pConn *PortConn) SetReadDeadline(t time.Time) error {
|
||||
return pConn.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (pConn *PortConn) SetWriteDeadline(t time.Time) error {
|
||||
return pConn.Conn.SetWriteDeadline(t)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package pmux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type PortListener struct {
|
||||
net.Listener
|
||||
connCh chan *PortConn
|
||||
addr net.Addr
|
||||
isClose bool
|
||||
}
|
||||
|
||||
func NewPortListener(connCh chan *PortConn, addr net.Addr) *PortListener {
|
||||
return &PortListener{
|
||||
connCh: connCh,
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (pListener *PortListener) Accept() (net.Conn, error) {
|
||||
if pListener.isClose {
|
||||
return nil, errors.New("the listener has closed")
|
||||
}
|
||||
conn := <-pListener.connCh
|
||||
if conn != nil {
|
||||
return conn, nil
|
||||
}
|
||||
return nil, errors.New("the listener has closed")
|
||||
}
|
||||
|
||||
func (pListener *PortListener) Close() error {
|
||||
//close
|
||||
if pListener.isClose {
|
||||
return errors.New("the listener has closed")
|
||||
}
|
||||
pListener.isClose = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pListener *PortListener) Addr() net.Addr {
|
||||
return pListener.addr
|
||||
}
|
166
lib/pmux/pmux.go
@ -1,166 +0,0 @@
|
||||
// This module is used for port reuse
|
||||
// Distinguish client, web manager , HTTP and HTTPS according to the difference of protocol
|
||||
package pmux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
HTTP_GET = 716984
|
||||
HTTP_POST = 807983
|
||||
HTTP_HEAD = 726965
|
||||
HTTP_PUT = 808585
|
||||
HTTP_DELETE = 686976
|
||||
HTTP_CONNECT = 677978
|
||||
HTTP_OPTIONS = 798084
|
||||
HTTP_TRACE = 848265
|
||||
CLIENT = 848384
|
||||
ACCEPT_TIME_OUT = 10
|
||||
)
|
||||
|
||||
type PortMux struct {
|
||||
net.Listener
|
||||
port int
|
||||
isClose bool
|
||||
managerHost string
|
||||
clientConn chan *PortConn
|
||||
httpConn chan *PortConn
|
||||
httpsConn chan *PortConn
|
||||
managerConn chan *PortConn
|
||||
}
|
||||
|
||||
func NewPortMux(port int, managerHost string) *PortMux {
|
||||
pMux := &PortMux{
|
||||
managerHost: managerHost,
|
||||
port: port,
|
||||
clientConn: make(chan *PortConn),
|
||||
httpConn: make(chan *PortConn),
|
||||
httpsConn: make(chan *PortConn),
|
||||
managerConn: make(chan *PortConn),
|
||||
}
|
||||
pMux.Start()
|
||||
return pMux
|
||||
}
|
||||
|
||||
func (pMux *PortMux) Start() error {
|
||||
// Port multiplexing is based on TCP only
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+strconv.Itoa(pMux.port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pMux.Listener, err = net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
conn, err := pMux.Listener.Accept()
|
||||
if err != nil {
|
||||
logs.Warn(err)
|
||||
//close
|
||||
pMux.Close()
|
||||
}
|
||||
go pMux.process(conn)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pMux *PortMux) process(conn net.Conn) {
|
||||
// Recognition according to different signs
|
||||
// read 3 byte
|
||||
buf := make([]byte, 3)
|
||||
if n, err := io.ReadFull(conn, buf); err != nil || n != 3 {
|
||||
return
|
||||
}
|
||||
var ch chan *PortConn
|
||||
var rs []byte
|
||||
var buffer bytes.Buffer
|
||||
var readMore = false
|
||||
switch common.BytesToNum(buf) {
|
||||
case HTTP_CONNECT, HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_OPTIONS, HTTP_POST, HTTP_PUT, HTTP_TRACE: //http and manager
|
||||
buffer.Reset()
|
||||
r := bufio.NewReader(conn)
|
||||
buffer.Write(buf)
|
||||
for {
|
||||
b, _, err := r.ReadLine()
|
||||
if err != nil {
|
||||
logs.Warn("read line error", err.Error())
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
buffer.Write(b)
|
||||
buffer.Write([]byte("\r\n"))
|
||||
if strings.Index(string(b), "Host:") == 0 || strings.Index(string(b), "host:") == 0 {
|
||||
// Remove host and space effects
|
||||
str := strings.Replace(string(b), "Host:", "", -1)
|
||||
str = strings.Replace(str, "host:", "", -1)
|
||||
str = strings.TrimSpace(str)
|
||||
// Determine whether it is the same as the manager domain name
|
||||
if common.GetIpByAddr(str) == pMux.managerHost {
|
||||
ch = pMux.managerConn
|
||||
} else {
|
||||
ch = pMux.httpConn
|
||||
}
|
||||
b, _ := r.Peek(r.Buffered())
|
||||
buffer.Write(b)
|
||||
rs = buffer.Bytes()
|
||||
break
|
||||
}
|
||||
}
|
||||
case CLIENT: // client connection
|
||||
ch = pMux.clientConn
|
||||
default: // https
|
||||
readMore = true
|
||||
ch = pMux.httpsConn
|
||||
}
|
||||
if len(rs) == 0 {
|
||||
rs = buf
|
||||
}
|
||||
timer := time.NewTimer(ACCEPT_TIME_OUT)
|
||||
select {
|
||||
case <-timer.C:
|
||||
case ch <- newPortConn(conn, rs, readMore):
|
||||
}
|
||||
}
|
||||
|
||||
func (pMux *PortMux) Close() error {
|
||||
if pMux.isClose {
|
||||
return errors.New("the port pmux has closed")
|
||||
}
|
||||
pMux.isClose = true
|
||||
close(pMux.clientConn)
|
||||
close(pMux.httpsConn)
|
||||
close(pMux.httpConn)
|
||||
close(pMux.managerConn)
|
||||
return pMux.Listener.Close()
|
||||
}
|
||||
|
||||
func (pMux *PortMux) GetClientListener() net.Listener {
|
||||
return NewPortListener(pMux.clientConn, pMux.Listener.Addr())
|
||||
}
|
||||
|
||||
func (pMux *PortMux) GetHttpListener() net.Listener {
|
||||
return NewPortListener(pMux.httpConn, pMux.Listener.Addr())
|
||||
}
|
||||
|
||||
func (pMux *PortMux) GetHttpsListener() net.Listener {
|
||||
return NewPortListener(pMux.httpsConn, pMux.Listener.Addr())
|
||||
}
|
||||
|
||||
func (pMux *PortMux) GetManagerListener() net.Listener {
|
||||
return NewPortListener(pMux.managerConn, pMux.Listener.Addr())
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package pmux
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
func TestPortMux_Close(t *testing.T) {
|
||||
logs.Reset()
|
||||
logs.EnableFuncCallDepth(true)
|
||||
logs.SetLogFuncCallDepth(3)
|
||||
|
||||
pMux := NewPortMux(8888, "Ds")
|
||||
go func() {
|
||||
if pMux.Start() != nil {
|
||||
logs.Warn("Error")
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Second * 3)
|
||||
go func() {
|
||||
l := pMux.GetHttpListener()
|
||||
conn, err := l.Accept()
|
||||
logs.Warn(conn, err)
|
||||
}()
|
||||
go func() {
|
||||
l := pMux.GetHttpListener()
|
||||
conn, err := l.Accept()
|
||||
logs.Warn(conn, err)
|
||||
}()
|
||||
go func() {
|
||||
l := pMux.GetHttpListener()
|
||||
conn, err := l.Accept()
|
||||
logs.Warn(conn, err)
|
||||
}()
|
||||
l := pMux.GetHttpListener()
|
||||
conn, err := l.Accept()
|
||||
logs.Warn(conn, err)
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package rate
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type rateConn struct {
|
||||
conn io.ReadWriteCloser
|
||||
rate *Rate
|
||||
}
|
||||
|
||||
func NewRateConn(conn io.ReadWriteCloser, rate *Rate) io.ReadWriteCloser {
|
||||
return &rateConn{
|
||||
conn: conn,
|
||||
rate: rate,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *rateConn) Read(b []byte) (n int, err error) {
|
||||
n, err = s.conn.Read(b)
|
||||
if s.rate != nil {
|
||||
s.rate.Get(int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *rateConn) Write(b []byte) (n int, err error) {
|
||||
n, err = s.conn.Write(b)
|
||||
if s.rate != nil {
|
||||
s.rate.Get(int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *rateConn) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package rate
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Rate struct {
|
||||
bucketSize int64
|
||||
bucketSurplusSize int64
|
||||
bucketAddSize int64
|
||||
stopChan chan bool
|
||||
NowRate int64
|
||||
}
|
||||
|
||||
func NewRate(addSize int64) *Rate {
|
||||
return &Rate{
|
||||
bucketSize: addSize * 2,
|
||||
bucketSurplusSize: 0,
|
||||
bucketAddSize: addSize,
|
||||
stopChan: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Rate) Start() {
|
||||
go s.session()
|
||||
}
|
||||
|
||||
func (s *Rate) add(size int64) {
|
||||
if res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize {
|
||||
atomic.AddInt64(&s.bucketSurplusSize, res)
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(&s.bucketSurplusSize, size)
|
||||
}
|
||||
|
||||
//回桶
|
||||
func (s *Rate) ReturnBucket(size int64) {
|
||||
s.add(size)
|
||||
}
|
||||
|
||||
//停止
|
||||
func (s *Rate) Stop() {
|
||||
s.stopChan <- true
|
||||
}
|
||||
|
||||
func (s *Rate) Get(size int64) {
|
||||
if s.bucketSurplusSize >= size {
|
||||
atomic.AddInt64(&s.bucketSurplusSize, -size)
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if s.bucketSurplusSize >= size {
|
||||
atomic.AddInt64(&s.bucketSurplusSize, -size)
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Rate) session() {
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 {
|
||||
s.NowRate = rs
|
||||
} else {
|
||||
s.NowRate = s.bucketSize - s.bucketSurplusSize
|
||||
}
|
||||
s.add(s.bucketAddSize)
|
||||
case <-s.stopChan:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package sheap
|
||||
|
||||
type IntHeap []int64
|
||||
|
||||
func (h IntHeap) Len() int { return len(h) }
|
||||
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
|
||||
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
|
||||
func (h *IntHeap) Push(x interface{}) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
*h = append(*h, x.(int64))
|
||||
}
|
||||
|
||||
func (h *IntHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*h = old[0 : n-1]
|
||||
return x
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package version
|
||||
|
||||
const VERSION = "0.26.10"
|
||||
|
||||
// Compulsory minimum version, Minimum downward compatibility to this version
|
||||
func GetVersion() string {
|
||||
return "0.26.0"
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"ehang.io/nps/lib/pmux"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
var pMux *pmux.PortMux
|
||||
var bridgePort string
|
||||
var httpsPort string
|
||||
var httpPort string
|
||||
var webPort string
|
||||
|
||||
func InitConnectionService() {
|
||||
bridgePort = beego.AppConfig.String("bridge_port")
|
||||
httpsPort = beego.AppConfig.String("https_proxy_port")
|
||||
httpPort = beego.AppConfig.String("http_proxy_port")
|
||||
webPort = beego.AppConfig.String("web_port")
|
||||
|
||||
if httpPort == bridgePort || httpsPort == bridgePort || webPort == bridgePort {
|
||||
port, err := strconv.Atoi(bridgePort)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
pMux = pmux.NewPortMux(port, beego.AppConfig.String("web_host"))
|
||||
}
|
||||
}
|
||||
|
||||
func GetBridgeListener(tp string) (net.Listener, error) {
|
||||
logs.Info("server start, the bridge type is %s, the bridge port is %s", tp, bridgePort)
|
||||
var p int
|
||||
var err error
|
||||
if p, err = strconv.Atoi(bridgePort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pMux != nil {
|
||||
return pMux.GetClientListener(), nil
|
||||
}
|
||||
return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(beego.AppConfig.String("bridge_ip")), p, ""})
|
||||
}
|
||||
|
||||
func GetHttpListener() (net.Listener, error) {
|
||||
if pMux != nil && httpPort == bridgePort {
|
||||
logs.Info("start http listener, port is", bridgePort)
|
||||
return pMux.GetHttpListener(), nil
|
||||
}
|
||||
logs.Info("start http listener, port is", httpPort)
|
||||
return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpPort)
|
||||
}
|
||||
|
||||
func GetHttpsListener() (net.Listener, error) {
|
||||
if pMux != nil && httpsPort == bridgePort {
|
||||
logs.Info("start https listener, port is", bridgePort)
|
||||
return pMux.GetHttpsListener(), nil
|
||||
}
|
||||
logs.Info("start https listener, port is", httpsPort)
|
||||
return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpsPort)
|
||||
}
|
||||
|
||||
func GetWebManagerListener() (net.Listener, error) {
|
||||
if pMux != nil && webPort == bridgePort {
|
||||
logs.Info("Web management start, access port is", bridgePort)
|
||||
return pMux.GetManagerListener(), nil
|
||||
}
|
||||
logs.Info("web management start, access port is", webPort)
|
||||
return getTcpListener(beego.AppConfig.String("web_ip"), webPort)
|
||||
}
|
||||
|
||||
func getTcpListener(ip, p string) (net.Listener, error) {
|
||||
port, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
if ip == "" {
|
||||
ip = "0.0.0.0"
|
||||
}
|
||||
return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(ip), port, ""})
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"ehang.io/nps/bridge"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/file"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type NetBridge interface {
|
||||
SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error)
|
||||
}
|
||||
|
||||
//BaseServer struct
|
||||
type BaseServer struct {
|
||||
id int
|
||||
bridge NetBridge
|
||||
task *file.Tunnel
|
||||
errorContent []byte
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer {
|
||||
return &BaseServer{
|
||||
bridge: bridge,
|
||||
task: task,
|
||||
errorContent: nil,
|
||||
Mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
//add the flow
|
||||
func (s *BaseServer) FlowAdd(in, out int64) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.task.Flow.ExportFlow += out
|
||||
s.task.Flow.InletFlow += in
|
||||
}
|
||||
|
||||
//change the flow
|
||||
func (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
host.Flow.ExportFlow += out
|
||||
host.Flow.InletFlow += in
|
||||
}
|
||||
|
||||
//write fail bytes to the connection
|
||||
func (s *BaseServer) writeConnFail(c net.Conn) {
|
||||
c.Write([]byte(common.ConnectionFailBytes))
|
||||
c.Write(s.errorContent)
|
||||
}
|
||||
|
||||
//auth check
|
||||
func (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error {
|
||||
if u != "" && p != "" && !common.CheckAuth(r, u, p) {
|
||||
c.Write([]byte(common.UnauthorizedBytes))
|
||||
c.Close()
|
||||
return errors.New("401 Unauthorized")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//check flow limit of the client ,and decrease the allow num of client
|
||||
func (s *BaseServer) CheckFlowAndConnNum(client *file.Client) error {
|
||||
if client.Flow.FlowLimit > 0 && (client.Flow.FlowLimit<<20) < (client.Flow.ExportFlow+client.Flow.InletFlow) {
|
||||
return errors.New("Traffic exceeded")
|
||||
}
|
||||
if !client.GetConn() {
|
||||
return errors.New("Connections exceed the current client limit")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//create a new connection and start bytes copying
|
||||
func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, rb []byte, tp string, f func(), flow *file.Flow, localProxy bool) error {
|
||||
link := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String(), localProxy)
|
||||
if target, err := s.bridge.SendLinkInfo(client.Id, link, s.task); err != nil {
|
||||
logs.Warn("get connection from client id %d error %s", client.Id, err.Error())
|
||||
c.Close()
|
||||
return err
|
||||
} else {
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
conn.CopyWaitGroup(target, c.Conn, link.Crypt, link.Compress, client.Rate, flow, true, rb)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,275 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"ehang.io/nps/bridge"
|
||||
"ehang.io/nps/lib/cache"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/file"
|
||||
"ehang.io/nps/server/connection"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type httpServer struct {
|
||||
BaseServer
|
||||
httpPort int
|
||||
httpsPort int
|
||||
httpServer *http.Server
|
||||
httpsServer *http.Server
|
||||
httpsListener net.Listener
|
||||
useCache bool
|
||||
addOrigin bool
|
||||
cache *cache.Cache
|
||||
cacheLen int
|
||||
}
|
||||
|
||||
func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int, addOrigin bool) *httpServer {
|
||||
httpServer := &httpServer{
|
||||
BaseServer: BaseServer{
|
||||
task: c,
|
||||
bridge: bridge,
|
||||
Mutex: sync.Mutex{},
|
||||
},
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
useCache: useCache,
|
||||
cacheLen: cacheLen,
|
||||
addOrigin: addOrigin,
|
||||
}
|
||||
if useCache {
|
||||
httpServer.cache = cache.New(cacheLen)
|
||||
}
|
||||
return httpServer
|
||||
}
|
||||
|
||||
func (s *httpServer) Start() error {
|
||||
var err error
|
||||
if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil {
|
||||
s.errorContent = []byte("nps 404")
|
||||
}
|
||||
if s.httpPort > 0 {
|
||||
s.httpServer = s.NewServer(s.httpPort, "http")
|
||||
go func() {
|
||||
l, err := connection.GetHttpListener()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
err = s.httpServer.Serve(l)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if s.httpsPort > 0 {
|
||||
s.httpsServer = s.NewServer(s.httpsPort, "https")
|
||||
go func() {
|
||||
s.httpsListener, err = connection.GetHttpsListener()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
logs.Error(NewHttpsServer(s.httpsListener, s.bridge, s.useCache, s.cacheLen).Start())
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *httpServer) Close() error {
|
||||
if s.httpsListener != nil {
|
||||
s.httpsListener.Close()
|
||||
}
|
||||
if s.httpsServer != nil {
|
||||
s.httpsServer.Close()
|
||||
}
|
||||
if s.httpServer != nil {
|
||||
s.httpServer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) {
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c, _, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
}
|
||||
s.handleHttp(conn.NewConn(c), r)
|
||||
}
|
||||
|
||||
func (s *httpServer) handleHttp(c *conn.Conn, r *http.Request) {
|
||||
var (
|
||||
host *file.Host
|
||||
target net.Conn
|
||||
err error
|
||||
connClient io.ReadWriteCloser
|
||||
scheme = r.URL.Scheme
|
||||
lk *conn.Link
|
||||
targetAddr string
|
||||
lenConn *conn.LenConn
|
||||
isReset bool
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
defer func() {
|
||||
if connClient != nil {
|
||||
connClient.Close()
|
||||
} else {
|
||||
s.writeConnFail(c.Conn)
|
||||
}
|
||||
c.Close()
|
||||
}()
|
||||
reset:
|
||||
if isReset {
|
||||
host.Client.AddConn()
|
||||
}
|
||||
if host, err = file.GetDb().GetInfoByHost(r.Host, r); err != nil {
|
||||
logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
|
||||
return
|
||||
}
|
||||
if err := s.CheckFlowAndConnNum(host.Client); err != nil {
|
||||
logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
|
||||
return
|
||||
}
|
||||
if !isReset {
|
||||
defer host.Client.AddConn()
|
||||
}
|
||||
if err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
|
||||
logs.Warn("auth error", err, r.RemoteAddr)
|
||||
return
|
||||
}
|
||||
if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
|
||||
logs.Warn(err.Error())
|
||||
return
|
||||
}
|
||||
lk = conn.NewLink("http", targetAddr, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr, host.Target.LocalProxy)
|
||||
if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, nil); err != nil {
|
||||
logs.Notice("connect to target %s error %s", lk.Host, err)
|
||||
return
|
||||
}
|
||||
connClient = conn.GetConn(target, lk.Crypt, lk.Compress, host.Client.Rate, true)
|
||||
|
||||
//read from inc-client
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
isReset = false
|
||||
defer connClient.Close()
|
||||
defer func() {
|
||||
wg.Done()
|
||||
if !isReset {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil || r == nil {
|
||||
// if there got broken pipe, http.ReadResponse will get a nil
|
||||
return
|
||||
} else {
|
||||
//if the cache is start and the response is in the extension,store the response to the cache list
|
||||
if s.useCache && r.URL != nil && strings.Contains(r.URL.Path, ".") {
|
||||
b, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Write(b)
|
||||
host.Flow.Add(0, int64(len(b)))
|
||||
s.cache.Add(filepath.Join(host.Host, r.URL.Path), b)
|
||||
} else {
|
||||
lenConn := conn.NewLenConn(c)
|
||||
if err := resp.Write(lenConn); err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
host.Flow.Add(0, int64(lenConn.Len))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
//if the cache start and the request is in the cache list, return the cache
|
||||
if s.useCache {
|
||||
if v, ok := s.cache.Get(filepath.Join(host.Host, r.URL.Path)); ok {
|
||||
n, err := c.Write(v.([]byte))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
logs.Trace("%s request, method %s, host %s, url %s, remote address %s, return cache", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String())
|
||||
host.Flow.Add(0, int64(n))
|
||||
//if return cache and does not create a new conn with client and Connection is not set or close, close the connection.
|
||||
if strings.ToLower(r.Header.Get("Connection")) == "close" || strings.ToLower(r.Header.Get("Connection")) == "" {
|
||||
break
|
||||
}
|
||||
goto readReq
|
||||
}
|
||||
}
|
||||
|
||||
//change the host and header and set proxy setting
|
||||
common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String(), s.addOrigin)
|
||||
logs.Trace("%s request, method %s, host %s, url %s, remote address %s, target %s", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String(), lk.Host)
|
||||
//write
|
||||
lenConn = conn.NewLenConn(connClient)
|
||||
if err := r.Write(lenConn); err != nil {
|
||||
logs.Error(err)
|
||||
break
|
||||
}
|
||||
host.Flow.Add(int64(lenConn.Len), 0)
|
||||
|
||||
readReq:
|
||||
//read req from connection
|
||||
if r, err = http.ReadRequest(bufio.NewReader(c)); err != nil {
|
||||
break
|
||||
}
|
||||
r.URL.Scheme = scheme
|
||||
//What happened ,Why one character less???
|
||||
r.Method = resetReqMethod(r.Method)
|
||||
if hostTmp, err := file.GetDb().GetInfoByHost(r.Host, r); err != nil {
|
||||
logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
|
||||
break
|
||||
} else if host != hostTmp {
|
||||
host = hostTmp
|
||||
isReset = true
|
||||
connClient.Close()
|
||||
goto reset
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func resetReqMethod(method string) string {
|
||||
if method == "ET" {
|
||||
return "GET"
|
||||
}
|
||||
if method == "OST" {
|
||||
return "POST"
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func (s *httpServer) NewServer(port int, scheme string) *http.Server {
|
||||
return &http.Server{
|
||||
Addr: ":" + strconv.Itoa(port),
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Scheme = scheme
|
||||
s.handleTunneling(w, r)
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
}
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"ehang.io/nps/lib/cache"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/crypt"
|
||||
"ehang.io/nps/lib/file"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type HttpsServer struct {
|
||||
httpServer
|
||||
listener net.Listener
|
||||
httpsListenerMap sync.Map
|
||||
}
|
||||
|
||||
func NewHttpsServer(l net.Listener, bridge NetBridge, useCache bool, cacheLen int) *HttpsServer {
|
||||
https := &HttpsServer{listener: l}
|
||||
https.bridge = bridge
|
||||
https.useCache = useCache
|
||||
if useCache {
|
||||
https.cache = cache.New(cacheLen)
|
||||
}
|
||||
return https
|
||||
}
|
||||
|
||||
//start https server
|
||||
func (https *HttpsServer) Start() error {
|
||||
if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b {
|
||||
conn.Accept(https.listener, func(c net.Conn) {
|
||||
https.handleHttps(c)
|
||||
})
|
||||
} else {
|
||||
//start the default listener
|
||||
certFile := beego.AppConfig.String("https_default_cert_file")
|
||||
keyFile := beego.AppConfig.String("https_default_key_file")
|
||||
if common.FileExists(certFile) && common.FileExists(keyFile) {
|
||||
l := NewHttpsListener(https.listener)
|
||||
https.NewHttps(l, certFile, keyFile)
|
||||
https.httpsListenerMap.Store("default", l)
|
||||
}
|
||||
conn.Accept(https.listener, func(c net.Conn) {
|
||||
serverName, rb := GetServerNameFromClientHello(c)
|
||||
//if the clientHello does not contains sni ,use the default ssl certificate
|
||||
if serverName == "" {
|
||||
serverName = "default"
|
||||
}
|
||||
var l *HttpsListener
|
||||
if v, ok := https.httpsListenerMap.Load(serverName); ok {
|
||||
l = v.(*HttpsListener)
|
||||
} else {
|
||||
r := buildHttpsRequest(serverName)
|
||||
if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {
|
||||
c.Close()
|
||||
logs.Notice("the url %s can't be parsed!,remote addr %s", serverName, c.RemoteAddr().String())
|
||||
return
|
||||
} else {
|
||||
if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {
|
||||
//if the host cert file or key file is not set ,use the default file
|
||||
if v, ok := https.httpsListenerMap.Load("default"); ok {
|
||||
l = v.(*HttpsListener)
|
||||
} else {
|
||||
c.Close()
|
||||
logs.Error("the key %s cert %s file is not exist", host.KeyFilePath, host.CertFilePath)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
l = NewHttpsListener(https.listener)
|
||||
https.NewHttps(l, host.CertFilePath, host.KeyFilePath)
|
||||
https.httpsListenerMap.Store(serverName, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
acceptConn := conn.NewConn(c)
|
||||
acceptConn.Rb = rb
|
||||
l.acceptConn <- acceptConn
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close
|
||||
func (https *HttpsServer) Close() error {
|
||||
return https.listener.Close()
|
||||
}
|
||||
|
||||
// new https server by cert and key file
|
||||
func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {
|
||||
go func() {
|
||||
logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile))
|
||||
}()
|
||||
}
|
||||
|
||||
//handle the https which is just proxy to other client
|
||||
func (https *HttpsServer) handleHttps(c net.Conn) {
|
||||
hostName, rb := GetServerNameFromClientHello(c)
|
||||
var targetAddr string
|
||||
r := buildHttpsRequest(hostName)
|
||||
var host *file.Host
|
||||
var err error
|
||||
if host, err = file.GetDb().GetInfoByHost(hostName, r); err != nil {
|
||||
c.Close()
|
||||
logs.Notice("the url %s can't be parsed!", hostName)
|
||||
return
|
||||
}
|
||||
if err := https.CheckFlowAndConnNum(host.Client); err != nil {
|
||||
logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
defer host.Client.AddConn()
|
||||
if err = https.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
|
||||
logs.Warn("auth error", err, r.RemoteAddr)
|
||||
return
|
||||
}
|
||||
if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
|
||||
logs.Warn(err.Error())
|
||||
}
|
||||
logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String())
|
||||
https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy)
|
||||
}
|
||||
|
||||
type HttpsListener struct {
|
||||
acceptConn chan *conn.Conn
|
||||
parentListener net.Listener
|
||||
}
|
||||
|
||||
// https listener
|
||||
func NewHttpsListener(l net.Listener) *HttpsListener {
|
||||
return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}
|
||||
}
|
||||
|
||||
// accept
|
||||
func (httpsListener *HttpsListener) Accept() (net.Conn, error) {
|
||||
httpsConn := <-httpsListener.acceptConn
|
||||
if httpsConn == nil {
|
||||
return nil, errors.New("get connection error")
|
||||
}
|
||||
return httpsConn, nil
|
||||
}
|
||||
|
||||
// close
|
||||
func (httpsListener *HttpsListener) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addr
|
||||
func (httpsListener *HttpsListener) Addr() net.Addr {
|
||||
return httpsListener.parentListener.Addr()
|
||||
}
|
||||
|
||||
// get server name from connection by read client hello bytes
|
||||
func GetServerNameFromClientHello(c net.Conn) (string, []byte) {
|
||||
buf := make([]byte, 4096)
|
||||
data := make([]byte, 4096)
|
||||
n, err := c.Read(buf)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
if n < 42 {
|
||||
return "", nil
|
||||
}
|
||||
copy(data, buf[:n])
|
||||
clientHello := new(crypt.ClientHelloMsg)
|
||||
clientHello.Unmarshal(data[5:n])
|
||||
return clientHello.GetServerName(), buf[:n]
|
||||
}
|
||||
|
||||
// build https request
|
||||
func buildHttpsRequest(hostName string) *http.Request {
|
||||
r := new(http.Request)
|
||||
r.RequestURI = "/"
|
||||
r.URL = new(url.URL)
|
||||
r.URL.Scheme = "https"
|
||||
r.Host = hostName
|
||||
return r
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type P2PServer struct {
|
||||
BaseServer
|
||||
p2pPort int
|
||||
p2p map[string]*p2p
|
||||
listener *net.UDPConn
|
||||
}
|
||||
|
||||
type p2p struct {
|
||||
visitorAddr *net.UDPAddr
|
||||
providerAddr *net.UDPAddr
|
||||
}
|
||||
|
||||
func NewP2PServer(p2pPort int) *P2PServer {
|
||||
return &P2PServer{
|
||||
p2pPort: p2pPort,
|
||||
p2p: make(map[string]*p2p),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *P2PServer) Start() error {
|
||||
logs.Info("start p2p server port", s.p2pPort)
|
||||
var err error
|
||||
s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), s.p2pPort, ""})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
buf := common.BufPoolUdp.Get().([]byte)
|
||||
n, addr, err := s.listener.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go s.handleP2P(addr, string(buf[:n]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *P2PServer) handleP2P(addr *net.UDPAddr, str string) {
|
||||
var (
|
||||
v *p2p
|
||||
ok bool
|
||||
)
|
||||
arr := strings.Split(str, common.CONN_DATA_SEQ)
|
||||
if len(arr) < 2 {
|
||||
return
|
||||
}
|
||||
if v, ok = s.p2p[arr[0]]; !ok {
|
||||
v = new(p2p)
|
||||
s.p2p[arr[0]] = v
|
||||
}
|
||||
logs.Trace("new p2p connection ,role %s , password %s ,local address %s", arr[1], arr[0], addr.String())
|
||||
if arr[1] == common.WORK_P2P_VISITOR {
|
||||
v.visitorAddr = addr
|
||||
for i := 20; i > 0; i-- {
|
||||
if v.providerAddr != nil {
|
||||
s.listener.WriteTo([]byte(v.providerAddr.String()), v.visitorAddr)
|
||||
s.listener.WriteTo([]byte(v.visitorAddr.String()), v.providerAddr)
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
delete(s.p2p, arr[0])
|
||||
} else {
|
||||
v.providerAddr = addr
|
||||
}
|
||||
}
|
@ -1,395 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/conn"
|
||||
"ehang.io/nps/lib/file"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
ipV4 = 1
|
||||
domainName = 3
|
||||
ipV6 = 4
|
||||
connectMethod = 1
|
||||
bindMethod = 2
|
||||
associateMethod = 3
|
||||
// The maximum packet size of any udp Associate packet, based on ethernet's max size,
|
||||
// minus the IP and UDP headers. IPv4 has a 20 byte header, UDP adds an
|
||||
// additional 4 bytes. This is a total overhead of 24 bytes. Ethernet's
|
||||
// max packet size is 1500 bytes, 1500 - 24 = 1476.
|
||||
maxUDPPacketSize = 1476
|
||||
)
|
||||
|
||||
const (
|
||||
succeeded uint8 = iota
|
||||
serverFailure
|
||||
notAllowed
|
||||
networkUnreachable
|
||||
hostUnreachable
|
||||
connectionRefused
|
||||
ttlExpired
|
||||
commandNotSupported
|
||||
addrTypeNotSupported
|
||||
)
|
||||
|
||||
const (
|
||||
UserPassAuth = uint8(2)
|
||||
userAuthVersion = uint8(1)
|
||||
authSuccess = uint8(0)
|
||||
authFailure = uint8(1)
|
||||
)
|
||||
|
||||
type Sock5ModeServer struct {
|
||||
BaseServer
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
//req
|
||||
func (s *Sock5ModeServer) handleRequest(c net.Conn) {
|
||||
/*
|
||||
The SOCKS request is formed as follows:
|
||||
+----+-----+-------+------+----------+----------+
|
||||
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
||||
+----+-----+-------+------+----------+----------+
|
||||
| 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||
+----+-----+-------+------+----------+----------+
|
||||
*/
|
||||
header := make([]byte, 3)
|
||||
|
||||
_, err := io.ReadFull(c, header)
|
||||
|
||||
if err != nil {
|
||||
logs.Warn("illegal request", err)
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
switch header[1] {
|
||||
case connectMethod:
|
||||
s.handleConnect(c)
|
||||
case bindMethod:
|
||||
s.handleBind(c)
|
||||
case associateMethod:
|
||||
s.handleUDP(c)
|
||||
default:
|
||||
s.sendReply(c, commandNotSupported)
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
//reply
|
||||
func (s *Sock5ModeServer) sendReply(c net.Conn, rep uint8) {
|
||||
reply := []byte{
|
||||
5,
|
||||
rep,
|
||||
0,
|
||||
1,
|
||||
}
|
||||
|
||||
localAddr := c.LocalAddr().String()
|
||||
localHost, localPort, _ := net.SplitHostPort(localAddr)
|
||||
ipBytes := net.ParseIP(localHost).To4()
|
||||
nPort, _ := strconv.Atoi(localPort)
|
||||
reply = append(reply, ipBytes...)
|
||||
portBytes := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(portBytes, uint16(nPort))
|
||||
reply = append(reply, portBytes...)
|
||||
|
||||
c.Write(reply)
|
||||
}
|
||||
|
||||
//do conn
|
||||
func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) {
|
||||
addrType := make([]byte, 1)
|
||||
c.Read(addrType)
|
||||
var host string
|
||||
switch addrType[0] {
|
||||
case ipV4:
|
||||
ipv4 := make(net.IP, net.IPv4len)
|
||||
c.Read(ipv4)
|
||||
host = ipv4.String()
|
||||
case ipV6:
|
||||
ipv6 := make(net.IP, net.IPv6len)
|
||||
c.Read(ipv6)
|
||||
host = ipv6.String()
|
||||
case domainName:
|
||||
var domainLen uint8
|
||||
binary.Read(c, binary.BigEndian, &domainLen)
|
||||
domain := make([]byte, domainLen)
|
||||
c.Read(domain)
|
||||
host = string(domain)
|
||||
default:
|
||||
s.sendReply(c, addrTypeNotSupported)
|
||||
return
|
||||
}
|
||||
|
||||
var port uint16
|
||||
binary.Read(c, binary.BigEndian, &port)
|
||||
// connect to host
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
var ltype string
|
||||
if command == associateMethod {
|
||||
ltype = common.CONN_UDP
|
||||
} else {
|
||||
ltype = common.CONN_TCP
|
||||
}
|
||||
s.DealClient(conn.NewConn(c), s.task.Client, addr, nil, ltype, func() {
|
||||
s.sendReply(c, succeeded)
|
||||
}, s.task.Flow, s.task.Target.LocalProxy)
|
||||
return
|
||||
}
|
||||
|
||||
//conn
|
||||
func (s *Sock5ModeServer) handleConnect(c net.Conn) {
|
||||
s.doConnect(c, connectMethod)
|
||||
}
|
||||
|
||||
// passive mode
|
||||
func (s *Sock5ModeServer) handleBind(c net.Conn) {
|
||||
}
|
||||
func (s *Sock5ModeServer) sendUdpReply(writeConn net.Conn, c net.Conn, rep uint8, serverIp string) {
|
||||
reply := []byte{
|
||||
5,
|
||||
rep,
|
||||
0,
|
||||
1,
|
||||
}
|
||||
localHost, localPort, _ := net.SplitHostPort(c.LocalAddr().String())
|
||||
localHost = serverIp
|
||||
ipBytes := net.ParseIP(localHost).To4()
|
||||
nPort, _ := strconv.Atoi(localPort)
|
||||
reply = append(reply, ipBytes...)
|
||||
portBytes := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(portBytes, uint16(nPort))
|
||||
reply = append(reply, portBytes...)
|
||||
writeConn.Write(reply)
|
||||
|
||||
}
|
||||
|
||||
func (s *Sock5ModeServer) handleUDP(c net.Conn) {
|
||||
defer c.Close()
|
||||
addrType := make([]byte, 1)
|
||||
c.Read(addrType)
|
||||
var host string
|
||||
switch addrType[0] {
|
||||
case ipV4:
|
||||
ipv4 := make(net.IP, net.IPv4len)
|
||||
c.Read(ipv4)
|
||||
host = ipv4.String()
|
||||
case ipV6:
|
||||
ipv6 := make(net.IP, net.IPv6len)
|
||||
c.Read(ipv6)
|
||||
host = ipv6.String()
|
||||
case domainName:
|
||||
var domainLen uint8
|
||||
binary.Read(c, binary.BigEndian, &domainLen)
|
||||
domain := make([]byte, domainLen)
|
||||
c.Read(domain)
|
||||
host = string(domain)
|
||||
default:
|
||||
s.sendReply(c, addrTypeNotSupported)
|
||||
return
|
||||
}
|
||||
//读取端口
|
||||
var port uint16
|
||||
binary.Read(c, binary.BigEndian, &port)
|
||||
logs.Warn(host, string(port))
|
||||
replyAddr, err := net.ResolveUDPAddr("udp", s.task.ServerIp+":0")
|
||||
if err != nil {
|
||||
logs.Error("build local reply addr error", err)
|
||||
return
|
||||
}
|
||||
reply, err := net.ListenUDP("udp", replyAddr)
|
||||
if err != nil {
|
||||
s.sendReply(c, addrTypeNotSupported)
|
||||
logs.Error("listen local reply udp port error")
|
||||
return
|
||||
}
|
||||
// reply the local addr
|
||||
s.sendUdpReply(c, reply, succeeded, common.GetServerIpByClientIp(c.RemoteAddr().(*net.TCPAddr).IP))
|
||||
defer reply.Close()
|
||||
// new a tunnel to client
|
||||
link := conn.NewLink("udp5", "", s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String(), false)
|
||||
target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task)
|
||||
if err != nil {
|
||||
logs.Warn("get connection from client id %d error %s", s.task.Client.Id, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var clientAddr net.Addr
|
||||
// copy buffer
|
||||
go func() {
|
||||
b := common.BufPoolUdp.Get().([]byte)
|
||||
defer common.BufPoolUdp.Put(b)
|
||||
defer c.Close()
|
||||
|
||||
for {
|
||||
n, laddr, err := reply.ReadFrom(b)
|
||||
if err != nil {
|
||||
logs.Error("read data from %s err %s", reply.LocalAddr().String(), err.Error())
|
||||
return
|
||||
}
|
||||
if clientAddr == nil {
|
||||
clientAddr = laddr
|
||||
}
|
||||
if _, err := target.Write(b[:n]); err != nil {
|
||||
logs.Error("write data to client error", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
var l int32
|
||||
b := common.BufPoolUdp.Get().([]byte)
|
||||
defer common.BufPoolUdp.Put(b)
|
||||
defer c.Close()
|
||||
for {
|
||||
if err := binary.Read(target, binary.LittleEndian, &l); err != nil || l >= common.PoolSizeUdp || l <= 0 {
|
||||
logs.Warn("read len bytes error", err.Error())
|
||||
return
|
||||
}
|
||||
binary.Read(target, binary.LittleEndian, b[:l])
|
||||
if err != nil {
|
||||
logs.Warn("read data form client error", err.Error())
|
||||
return
|
||||
}
|
||||
if _, err := reply.WriteTo(b[:l], clientAddr); err != nil {
|
||||
logs.Warn("write data to user ", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
b := common.BufPoolUdp.Get().([]byte)
|
||||
defer common.BufPoolUdp.Put(b)
|
||||
defer target.Close()
|
||||
for {
|
||||
_, err := c.Read(b)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//new conn
|
||||
func (s *Sock5ModeServer) handleConn(c net.Conn) {
|
||||
buf := make([]byte, 2)
|
||||
if _, err := io.ReadFull(c, buf); err != nil {
|
||||
logs.Warn("negotiation err", err)
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if version := buf[0]; version != 5 {
|
||||
logs.Warn("only support socks5, request from: ", c.RemoteAddr())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
nMethods := buf[1]
|
||||
|
||||
methods := make([]byte, nMethods)
|
||||
if len, err := c.Read(methods); len != int(nMethods) || err != nil {
|
||||
logs.Warn("wrong method")
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
if (s.task.Client.Cnf.U != "" && s.task.Client.Cnf.P != "") || (s.task.MultiAccount != nil && len(s.task.MultiAccount.AccountMap) > 0) {
|
||||
buf[1] = UserPassAuth
|
||||
c.Write(buf)
|
||||
if err := s.Auth(c); err != nil {
|
||||
c.Close()
|
||||
logs.Warn("Validation failed:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
buf[1] = 0
|
||||
c.Write(buf)
|
||||
}
|
||||
s.handleRequest(c)
|
||||
}
|
||||
|
||||
//socks5 auth
|
||||
func (s *Sock5ModeServer) Auth(c net.Conn) error {
|
||||
header := []byte{0, 0}
|
||||
if _, err := io.ReadAtLeast(c, header, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
if header[0] != userAuthVersion {
|
||||
return errors.New("验证方式不被支持")
|
||||
}
|
||||
userLen := int(header[1])
|
||||
user := make([]byte, userLen)
|
||||
if _, err := io.ReadAtLeast(c, user, userLen); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.Read(header[:1]); err != nil {
|
||||
return errors.New("密码长度获取错误")
|
||||
}
|
||||
passLen := int(header[0])
|
||||
pass := make([]byte, passLen)
|
||||
if _, err := io.ReadAtLeast(c, pass, passLen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var U, P string
|
||||
if s.task.MultiAccount != nil {
|
||||
// enable multi user auth
|
||||
U = string(user)
|
||||
var ok bool
|
||||
P, ok = s.task.MultiAccount.AccountMap[U]
|
||||
if !ok {
|
||||
return errors.New("验证不通过")
|
||||
}
|
||||
} else {
|
||||
U = s.task.Client.Cnf.U
|
||||
P = s.task.Client.Cnf.P
|
||||
}
|
||||
|
||||
if string(user) == U && string(pass) == P {
|
||||
if _, err := c.Write([]byte{userAuthVersion, authSuccess}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
if _, err := c.Write([]byte{userAuthVersion, authFailure}); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("验证不通过")
|
||||
}
|
||||
}
|
||||
|
||||
//start
|
||||
func (s *Sock5ModeServer) Start() error {
|
||||
return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) {
|
||||
if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
|
||||
logs.Warn("client id %d, task id %d, error %s, when socks5 connection", s.task.Client.Id, s.task.Id, err.Error())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr())
|
||||
s.handleConn(c)
|
||||
s.task.Client.AddConn()
|
||||
}, &s.listener)
|
||||
}
|
||||
|
||||
//new
|
||||
func NewSock5ModeServer(bridge NetBridge, task *file.Tunnel) *Sock5ModeServer {
|
||||
s := new(Sock5ModeServer)
|
||||
s.bridge = bridge
|
||||
s.task = task
|
||||
return s
|
||||
}
|
||||
|
||||
//close
|
||||
func (s *Sock5ModeServer) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|