mirror of
https://github.com/ehang-io/nps.git
synced 2025-07-02 04:00:42 +00:00
add new file
This commit is contained in:
parent
05b2e55f39
commit
c482967f8c
23
component/bridge/bridge.go
Normal file
23
component/bridge/bridge.go
Normal file
@ -0,0 +1,23 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"net"
|
||||
)
|
||||
|
||||
func StartTcpBridge(ln net.Listener, config *tls.Config, serverCheck, clientCheck func(string) bool) error {
|
||||
h, err := NewTcpServer(ln, config, serverCheck, clientCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.run()
|
||||
}
|
||||
|
||||
func StartQUICBridge(ln net.PacketConn, config *tls.Config, quicConfig *quic.Config, clientCheck func(string) bool) error {
|
||||
h, err := NewQUICServer(ln, config, quicConfig, clientCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.run()
|
||||
}
|
102
component/bridge/client.go
Normal file
102
component/bridge/client.go
Normal file
@ -0,0 +1,102 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"ehang.io/nps/transport"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
*manager
|
||||
tunnelId string
|
||||
clientId string
|
||||
control transport.Conn
|
||||
data transport.Conn
|
||||
httpClient *http.Client
|
||||
pingErrTimes int
|
||||
}
|
||||
|
||||
func NewClient(tunnelId string, clientId string, mg *manager) *client {
|
||||
c := &client{tunnelId: tunnelId, clientId: clientId, manager: mg}
|
||||
c.httpClient = &http.Client{Transport: &http.Transport{DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return c.NewControlConn()
|
||||
}}}
|
||||
go c.doPing()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) SetTunnel(conn transport.Conn, control bool) {
|
||||
if control {
|
||||
c.control = conn
|
||||
} else {
|
||||
c.data = conn
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) NewDataConn() (net.Conn, error) {
|
||||
if c.data == nil {
|
||||
return nil, errors.New("the data tunnel is not exist")
|
||||
}
|
||||
return c.data.Open()
|
||||
}
|
||||
|
||||
func (c *client) NewControlConn() (net.Conn, error) {
|
||||
if c.control == nil {
|
||||
return nil, errors.New("the data tunnel is not exist")
|
||||
}
|
||||
return c.control.Open()
|
||||
}
|
||||
|
||||
func (c *client) doPing() {
|
||||
for range time.NewTicker(time.Second * 5).C {
|
||||
if err := c.ping(); err != nil {
|
||||
c.pingErrTimes++
|
||||
logger.Error("do ping error", zap.Error(err))
|
||||
} else {
|
||||
logger.Debug("do ping success", zap.String("client id", c.clientId), zap.String("tunnel id", c.tunnelId))
|
||||
c.pingErrTimes = 0
|
||||
}
|
||||
if c.pingErrTimes > 3 {
|
||||
logger.Error("ping failed, close")
|
||||
c.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) close() {
|
||||
if c.data != nil {
|
||||
_ = c.data.Close()
|
||||
}
|
||||
if c.control != nil {
|
||||
_ = c.control.Close()
|
||||
}
|
||||
_ = c.RemoveClient(c)
|
||||
}
|
||||
|
||||
func (c *client) ping() error {
|
||||
conn, err := c.NewDataConn()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "data ping")
|
||||
}
|
||||
_, err = pb.WriteMessage(conn, &pb.ClientRequest{ConnType: &pb.ClientRequest_Ping{Ping: &pb.Ping{Now: time.Now().String()}}})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "data ping")
|
||||
}
|
||||
_, err = pb.ReadMessage(conn, &pb.Ping{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "data ping")
|
||||
}
|
||||
resp, err := c.httpClient.Get("http://nps.ehang.io/ping")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "control ping")
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
return nil
|
||||
}
|
60
component/bridge/manager.go
Normal file
60
component/bridge/manager.go
Normal file
@ -0,0 +1,60 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/lb"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/transport"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type manager struct {
|
||||
clients map[string]*client
|
||||
clientLb *lb.LoadBalancer
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewManager() *manager {
|
||||
return &manager{
|
||||
clients: make(map[string]*client),
|
||||
clientLb: lb.NewLoadBalancer(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) SetClient(clientId string, tunnelId string, isControl bool, conn transport.Conn) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
client, ok := m.clients[tunnelId]
|
||||
if !ok {
|
||||
client = NewClient(tunnelId, clientId, m)
|
||||
err := m.clientLb.SetClient(clientId, client)
|
||||
if err != nil {
|
||||
logger.Error("set client error", zap.Error(err), zap.String("clientId", clientId), zap.String("tunnelId", tunnelId))
|
||||
return err
|
||||
}
|
||||
m.clients[tunnelId] = client
|
||||
}
|
||||
client.SetTunnel(conn, isControl)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) GetDataConn(clientId string) (net.Conn, error) {
|
||||
c, err := m.clientLb.GetClient(clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.(*client).NewDataConn()
|
||||
}
|
||||
|
||||
func (m *manager) RemoveClient(client *client) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
err := m.clientLb.RemoveClient(client.clientId, client)
|
||||
if err != nil {
|
||||
logger.Error("remove client error", zap.Error(err), zap.String("clientId", client.clientId), zap.String("tunnelId", client.tunnelId))
|
||||
return err
|
||||
}
|
||||
delete(m.clients, client.tunnelId)
|
||||
return nil
|
||||
}
|
91
component/bridge/quic.go
Normal file
91
component/bridge/quic.go
Normal file
@ -0,0 +1,91 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"ehang.io/nps/transport"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type QUICServer struct {
|
||||
packetConn net.PacketConn
|
||||
tlsConfig *tls.Config
|
||||
config *quic.Config
|
||||
listener quic.Listener
|
||||
gp *ants.PoolWithFunc
|
||||
clientCheck func(string) bool
|
||||
manager *manager
|
||||
}
|
||||
|
||||
func NewQUICServer(packetConn net.PacketConn, tlsConfig *tls.Config, config *quic.Config, clientCheck func(string) bool) (*QUICServer, error) {
|
||||
qs := &QUICServer{
|
||||
packetConn: packetConn,
|
||||
tlsConfig: tlsConfig,
|
||||
config: config,
|
||||
clientCheck: clientCheck,
|
||||
manager: NewManager(),
|
||||
}
|
||||
var err error
|
||||
if qs.listener, err = quic.Listen(packetConn, tlsConfig, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qs.gp, err = ants.NewPoolWithFunc(1000000, func(i interface{}) {
|
||||
session := i.(quic.Session)
|
||||
logger.Debug("accept a session", zap.String("remote addr", session.RemoteAddr().String()))
|
||||
stream, err := session.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
logger.Warn("accept stream error", zap.Error(err))
|
||||
_ = session.CloseWithError(0, "check auth failed")
|
||||
return
|
||||
}
|
||||
cr := &pb.ConnRequest{}
|
||||
_, err = pb.ReadMessage(stream, cr)
|
||||
if err != nil {
|
||||
_ = session.CloseWithError(0, "check auth failed")
|
||||
logger.Warn("read message error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if !qs.clientCheck(cr.GetId()) {
|
||||
_ = session.CloseWithError(0, "check auth failed")
|
||||
logger.Error("check server id error", zap.String("id", cr.GetId()))
|
||||
_ = qs.responseClient(stream, false, "id check failed")
|
||||
return
|
||||
}
|
||||
qc := transport.NewQUIC(session)
|
||||
_ = qc.Client()
|
||||
|
||||
_ = qs.responseClient(stream, true, "success")
|
||||
err = qs.manager.SetClient(cr.GetId(), cr.GetNpcInfo().GetTunnelId(), cr.GetNpcInfo().GetIsControlTunnel(), qc)
|
||||
if err != nil {
|
||||
_ = session.CloseWithError(0, "check auth failed")
|
||||
logger.Error("set client error", zap.Error(err), zap.String("info", cr.String()))
|
||||
}
|
||||
})
|
||||
return qs, err
|
||||
}
|
||||
|
||||
func (qs *QUICServer) responseClient(conn io.Writer, success bool, msg string) error {
|
||||
_, err := pb.WriteMessage(conn, &pb.NpcResponse{Success: success, Message: msg})
|
||||
return err
|
||||
}
|
||||
|
||||
func (qs *QUICServer) run() error {
|
||||
for {
|
||||
session, err := qs.listener.Accept(context.Background())
|
||||
if err != nil {
|
||||
logger.Error("accept connection failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
err = qs.gp.Invoke(session)
|
||||
if err != nil {
|
||||
logger.Error("Invoke session error", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
116
component/bridge/tcp.go
Normal file
116
component/bridge/tcp.go
Normal file
@ -0,0 +1,116 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"ehang.io/nps/lib/pool"
|
||||
"ehang.io/nps/transport"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type tcpServer struct {
|
||||
config *tls.Config // config must contain root ca and server cert
|
||||
ln net.Listener
|
||||
gp *ants.PoolWithFunc
|
||||
manager *manager
|
||||
serverCheck func(id string) bool
|
||||
clientCheck func(id string) bool
|
||||
}
|
||||
|
||||
func NewTcpServer(ln net.Listener, config *tls.Config, serverCheck, clientCheck func(string) bool) (*tcpServer, error) {
|
||||
h := &tcpServer{
|
||||
config: config,
|
||||
ln: ln,
|
||||
serverCheck: serverCheck,
|
||||
clientCheck: clientCheck,
|
||||
manager: NewManager(),
|
||||
}
|
||||
var err error
|
||||
h.gp, err = ants.NewPoolWithFunc(1000000, func(i interface{}) {
|
||||
conn := i.(net.Conn)
|
||||
cr := &pb.ConnRequest{}
|
||||
_, err := pb.ReadMessage(conn, cr)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
logger.Warn("read message error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
switch cr.ConnType.(type) {
|
||||
case *pb.ConnRequest_AppInfo:
|
||||
h.handleApp(cr, conn)
|
||||
case *pb.ConnRequest_NpcInfo:
|
||||
h.handleClient(cr, conn)
|
||||
}
|
||||
})
|
||||
return h, err
|
||||
}
|
||||
|
||||
func (h *tcpServer) handleApp(cr *pb.ConnRequest, conn net.Conn) {
|
||||
if !h.serverCheck(cr.GetId()) {
|
||||
_ = conn.Close()
|
||||
logger.Error("check server id error", zap.String("id", cr.GetId()))
|
||||
return
|
||||
}
|
||||
clientConn, err := h.manager.GetDataConn(cr.GetAppInfo().GetNpcId())
|
||||
if err != nil {
|
||||
logger.Error("get client error", zap.Error(err), zap.String("app_info", cr.String()))
|
||||
return
|
||||
}
|
||||
_, err = pb.WriteMessage(clientConn, &pb.ClientRequest{ConnType: &pb.ClientRequest_AppInfo{AppInfo: cr.GetAppInfo()}})
|
||||
if err != nil {
|
||||
_ = clientConn.Close()
|
||||
_ = conn.Close()
|
||||
logger.Error("write app_info error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
_ = pool.CopyConnGoroutinePool.Invoke(pool.CopyConnGpParams{Writer: conn, Reader: clientConn, Wg: &wg})
|
||||
_ = pool.CopyConnGoroutinePool.Invoke(pool.CopyConnGpParams{Writer: clientConn, Reader: conn, Wg: &wg})
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (h *tcpServer) responseClient(conn io.Writer, success bool, msg string) error {
|
||||
_, err := pb.WriteMessage(conn, &pb.NpcResponse{Success: success, Message: msg})
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *tcpServer) handleClient(cr *pb.ConnRequest, conn net.Conn) {
|
||||
if !h.clientCheck(cr.GetId()) {
|
||||
_ = conn.Close()
|
||||
logger.Error("check server id error", zap.String("id", cr.GetId()))
|
||||
_ = h.responseClient(conn, false, "id check failed")
|
||||
return
|
||||
}
|
||||
yc := transport.NewYaMux(conn, nil)
|
||||
err := yc.Client()
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
_ = h.responseClient(conn, false, "client failed")
|
||||
logger.Error("new yamux client error", zap.Error(err), zap.String("remote address", conn.RemoteAddr().String()))
|
||||
return
|
||||
}
|
||||
_ = h.responseClient(conn, true, "success")
|
||||
err = h.manager.SetClient(cr.GetId(), cr.GetNpcInfo().GetTunnelId(), cr.GetNpcInfo().GetIsControlTunnel(), yc)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
logger.Error("set client error", zap.Error(err), zap.String("info", cr.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *tcpServer) run() error {
|
||||
h.ln = tls.NewListener(h.ln, h.config)
|
||||
for {
|
||||
conn, err := h.ln.Accept()
|
||||
if err != nil {
|
||||
logger.Error("Accept conn error", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
_ = h.gp.Invoke(conn)
|
||||
}
|
||||
}
|
129
component/client/client.go
Normal file
129
component/client/client.go
Normal file
@ -0,0 +1,129 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/rule"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
controlLn net.Listener
|
||||
dataLn net.Listener
|
||||
lastPongTime time.Time
|
||||
mux *http.ServeMux
|
||||
ticker *time.Ticker
|
||||
closeCh chan struct{}
|
||||
closed int32
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewClient(controlLn, dataLn net.Listener) *Client {
|
||||
return &Client{
|
||||
controlLn: controlLn,
|
||||
dataLn: dataLn,
|
||||
mux: &http.ServeMux{},
|
||||
ticker: time.NewTicker(time.Second * 5),
|
||||
closeCh: make(chan struct{}, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ping(writer http.ResponseWriter, request *http.Request) {
|
||||
c.lastPongTime = time.Now()
|
||||
_, err := io.WriteString(writer, "pong")
|
||||
if err != nil {
|
||||
logger.Warn("write pong error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
logger.Debug("write pong success")
|
||||
}
|
||||
|
||||
func (c *Client) Run() {
|
||||
c.mux.HandleFunc("/ping", c.ping)
|
||||
c.wg.Add(3)
|
||||
go c.handleControlConn()
|
||||
go c.handleDataConn()
|
||||
go c.checkPing()
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
func (c *Client) HasPong() bool {
|
||||
return time.Now().Sub(c.lastPongTime).Seconds() < 10
|
||||
}
|
||||
|
||||
func (c *Client) checkPing() {
|
||||
for {
|
||||
select {
|
||||
case <-c.ticker.C:
|
||||
if !c.lastPongTime.IsZero() && time.Now().Sub(c.lastPongTime).Seconds() > 15 && c.controlLn != nil {
|
||||
logger.Debug("close connection", zap.Time("lastPongTime", c.lastPongTime), zap.Time("now", time.Now()))
|
||||
_ = c.controlLn.Close()
|
||||
}
|
||||
case <-c.closeCh:
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handleDataConn() {
|
||||
h := &handler.DefaultHandler{}
|
||||
ac := &action.LocalAction{}
|
||||
err := ac.Init()
|
||||
if err != nil {
|
||||
logger.Warn("init action failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
appPr := &process.PbAppProcessor{}
|
||||
_ = appPr.Init(ac)
|
||||
h.AddRule(&rule.Rule{Handler: h, Process: appPr, Action: ac})
|
||||
|
||||
pingPr := &process.PbPingProcessor{}
|
||||
_ = appPr.Init(ac)
|
||||
h.AddRule(&rule.Rule{Handler: h, Process: pingPr, Action: ac})
|
||||
|
||||
var conn net.Conn
|
||||
for {
|
||||
conn, err = c.dataLn.Accept()
|
||||
if err != nil {
|
||||
logger.Error("accept connection failed", zap.Error(err))
|
||||
break
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
_, err = h.HandleConn(nil, enet.NewReaderConn(conn))
|
||||
if err != nil {
|
||||
logger.Warn("process failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
c.wg.Done()
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func (c *Client) handleControlConn() {
|
||||
err := http.Serve(c.controlLn, c.mux)
|
||||
if err != nil {
|
||||
logger.Error("http error", zap.Error(err))
|
||||
}
|
||||
c.wg.Done()
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
|
||||
c.closeCh <- struct{}{}
|
||||
c.ticker.Stop()
|
||||
_ = c.controlLn.Close()
|
||||
_ = c.dataLn.Close()
|
||||
}
|
||||
}
|
69
component/client/conn.go
Normal file
69
component/client/conn.go
Normal file
@ -0,0 +1,69 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"ehang.io/nps/transport"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TunnelCreator interface {
|
||||
NewMux(bridgeAddr string, message *pb.ConnRequest, config *tls.Config) (net.Listener, error)
|
||||
}
|
||||
|
||||
type BaseTunnelCreator struct{}
|
||||
|
||||
func (bc BaseTunnelCreator) handshake(npcInfo *pb.ConnRequest, rw io.ReadWriteCloser) error {
|
||||
_, err := pb.WriteMessage(rw, npcInfo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write handshake message")
|
||||
}
|
||||
var resp pb.NpcResponse
|
||||
_, err = pb.ReadMessage(rw, &resp)
|
||||
if err != nil || !resp.Success {
|
||||
return errors.Wrap(err, resp.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TcpTunnelCreator struct{ BaseTunnelCreator }
|
||||
|
||||
func (tc TcpTunnelCreator) NewMux(bridgeAddr string, message *pb.ConnRequest, config *tls.Config) (net.Listener, error) {
|
||||
conn, err := tls.Dial("tcp", bridgeAddr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tc.handshake(message, conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := transport.NewYaMux(conn, nil)
|
||||
return server, server.Server()
|
||||
}
|
||||
|
||||
type QUICTunnelCreator struct{ BaseTunnelCreator }
|
||||
|
||||
func (tc QUICTunnelCreator) NewMux(bridgeAddr string, message *pb.ConnRequest, config *tls.Config) (net.Listener, error) {
|
||||
session, err := quic.DialAddr(bridgeAddr, config, &quic.Config{
|
||||
MaxIncomingStreams: 1000000,
|
||||
MaxIncomingUniStreams: 1000000,
|
||||
MaxIdleTimeout: time.Minute,
|
||||
KeepAlive: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream, err := session.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tc.handshake(message, stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := transport.NewQUIC(session)
|
||||
return server, server.Server()
|
||||
}
|
44
component/client/npc.go
Normal file
44
component/client/npc.go
Normal file
@ -0,0 +1,44 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/lib/pb"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// StartNpc is used to connect to bridge
|
||||
// proto is quic or tcp
|
||||
// tlsConfig must contain a npc cert
|
||||
func StartNpc(proto string, bridgeAddr string, tlsConfig *tls.Config) error {
|
||||
id, err := cert.GetCertSnFromConfig(tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var creator TunnelCreator
|
||||
if proto == "quic" {
|
||||
creator = QUICTunnelCreator{}
|
||||
} else {
|
||||
creator = TcpTunnelCreator{}
|
||||
}
|
||||
connId := uuid.NewV1().String()
|
||||
retry:
|
||||
logger.Info("start connecting to bridge")
|
||||
controlLn, err := creator.NewMux(bridgeAddr,
|
||||
&pb.ConnRequest{Id: id, ConnType: &pb.ConnRequest_NpcInfo{NpcInfo: &pb.NpcInfo{TunnelId: connId, IsControlTunnel: true}}}, tlsConfig)
|
||||
if err != nil {
|
||||
logger.Error("new control connection error", zap.Error(err))
|
||||
goto retry
|
||||
}
|
||||
dataLn, err := creator.NewMux(bridgeAddr,
|
||||
&pb.ConnRequest{Id: id, ConnType: &pb.ConnRequest_NpcInfo{NpcInfo: &pb.NpcInfo{TunnelId: connId, IsControlTunnel: false}}}, tlsConfig)
|
||||
if err != nil {
|
||||
logger.Error("new data connection error", zap.Error(err))
|
||||
goto retry
|
||||
}
|
||||
c := NewClient(controlLn, dataLn)
|
||||
c.Run()
|
||||
goto retry
|
||||
}
|
1
component/component.go
Normal file
1
component/component.go
Normal file
@ -0,0 +1 @@
|
||||
package component
|
193
component/component_test.go
Normal file
193
component/component_test.go
Normal file
@ -0,0 +1,193 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"ehang.io/nps/component/bridge"
|
||||
"ehang.io/nps/component/client"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var createCertOnce sync.Once
|
||||
|
||||
func createCertFile(t *testing.T) (string, string) {
|
||||
createCertOnce.Do(func() {
|
||||
g := cert.NewX509Generator(pkix.Name{
|
||||
Country: []string{"CN"},
|
||||
Organization: []string{"Ehang.io"},
|
||||
OrganizationalUnit: []string{"nps"},
|
||||
Province: []string{"Beijing"},
|
||||
CommonName: "nps",
|
||||
Locality: []string{"Beijing"},
|
||||
})
|
||||
cert, key, err := g.CreateRootCa()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "root_cert.pem"), cert, 0600))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "root_key.pem"), key, 0600))
|
||||
assert.NoError(t, g.InitRootCa(cert, key))
|
||||
cert, key, err = g.CreateCert("bridge.nps.ehang.io")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "bridge_cert.pem"), cert, 0600))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "bridge_key.pem"), key, 0600))
|
||||
cert, key, err = g.CreateCert("client.nps.ehang.io")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "client_cert.pem"), cert, 0600))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "client_key.pem"), key, 0600))
|
||||
})
|
||||
return filepath.Join(os.TempDir(), "cert.pem"), filepath.Join(os.TempDir(), "key.pem")
|
||||
}
|
||||
|
||||
func TestTcpConnect(t *testing.T) {
|
||||
createCertFile(t)
|
||||
bridgeLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(os.TempDir(), "root_cert.pem"))
|
||||
assert.NoError(t, err)
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(buf)
|
||||
|
||||
crt, err := tls.LoadX509KeyPair(filepath.Join(os.TempDir(), "bridge_cert.pem"), filepath.Join(os.TempDir(), "bridge_key.pem"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
bridgeTlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{crt},
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: pool,
|
||||
}
|
||||
|
||||
crt, err = tls.LoadX509KeyPair(filepath.Join(os.TempDir(), "client_cert.pem"), filepath.Join(os.TempDir(), "client_key.pem"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
clientConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{crt},
|
||||
}
|
||||
go func() {
|
||||
assert.NoError(t, bridge.StartTcpBridge(bridgeLn, bridgeTlsConfig, func(s string) bool {
|
||||
return true
|
||||
}, func(s string) bool {
|
||||
sn, err := cert.GetCertSnFromConfig(clientConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, sn, s)
|
||||
return true
|
||||
}))
|
||||
}()
|
||||
var c *client.Client
|
||||
go func() {
|
||||
id, err := cert.GetCertSnFromConfig(clientConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
creator := client.TcpTunnelCreator{}
|
||||
connId := uuid.NewV1().String()
|
||||
controlLn, err := creator.NewMux(bridgeLn.Addr().String(),
|
||||
&pb.ConnRequest{Id: id, ConnType: &pb.ConnRequest_NpcInfo{NpcInfo: &pb.NpcInfo{TunnelId: connId, IsControlTunnel: true}}}, clientConfig)
|
||||
assert.NoError(t, err)
|
||||
dataLn, err := creator.NewMux(bridgeLn.Addr().String(),
|
||||
&pb.ConnRequest{Id: id, ConnType: &pb.ConnRequest_NpcInfo{NpcInfo: &pb.NpcInfo{TunnelId: connId, IsControlTunnel: false}}}, clientConfig)
|
||||
assert.NoError(t, err)
|
||||
c = client.NewClient(controlLn, dataLn)
|
||||
c.Run()
|
||||
}()
|
||||
timeout := time.NewTimer(time.Second * 30)
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if c != nil && c.HasPong() {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
case <-timeout.C:
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQUICConnect(t *testing.T) {
|
||||
createCertFile(t)
|
||||
bridgePacketConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(os.TempDir(), "root_cert.pem"))
|
||||
assert.NoError(t, err)
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(buf)
|
||||
|
||||
crt, err := tls.LoadX509KeyPair(filepath.Join(os.TempDir(), "bridge_cert.pem"), filepath.Join(os.TempDir(), "bridge_key.pem"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
bridgeTlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{crt},
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: pool,
|
||||
NextProtos: []string{"quic-nps"},
|
||||
}
|
||||
|
||||
crt, err = tls.LoadX509KeyPair(filepath.Join(os.TempDir(), "client_cert.pem"), filepath.Join(os.TempDir(), "client_key.pem"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
clientConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{crt},
|
||||
NextProtos: []string{"quic-nps"},
|
||||
}
|
||||
go func() {
|
||||
assert.NoError(t, bridge.StartQUICBridge(bridgePacketConn, bridgeTlsConfig, &quic.Config{
|
||||
MaxIncomingStreams: 1000000,
|
||||
MaxIncomingUniStreams: 1000000,
|
||||
MaxIdleTimeout: time.Minute,
|
||||
KeepAlive: true,
|
||||
}, func(s string) bool {
|
||||
sn, err := cert.GetCertSnFromConfig(clientConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, sn, s)
|
||||
return true
|
||||
}))
|
||||
}()
|
||||
var c *client.Client
|
||||
go func() {
|
||||
id, err := cert.GetCertSnFromConfig(clientConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
creator := client.QUICTunnelCreator{}
|
||||
connId := uuid.NewV1().String()
|
||||
controlLn, err := creator.NewMux(bridgePacketConn.LocalAddr().String(),
|
||||
&pb.ConnRequest{Id: id, ConnType: &pb.ConnRequest_NpcInfo{NpcInfo: &pb.NpcInfo{TunnelId: connId, IsControlTunnel: true}}}, clientConfig)
|
||||
assert.NoError(t, err)
|
||||
dataLn, err := creator.NewMux(bridgePacketConn.LocalAddr().String(),
|
||||
&pb.ConnRequest{Id: id, ConnType: &pb.ConnRequest_NpcInfo{NpcInfo: &pb.NpcInfo{TunnelId: connId, IsControlTunnel: false}}}, clientConfig)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, dataLn)
|
||||
assert.NotEmpty(t, controlLn)
|
||||
c = client.NewClient(controlLn, dataLn)
|
||||
c.Run()
|
||||
}()
|
||||
timeout := time.NewTimer(time.Second * 30)
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if c != nil && c.HasPong() {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
case <-timeout.C:
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
1
component/controller/asset
Submodule
1
component/controller/asset
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 07c2567751c711541a4ff24af00f2cd018c18eab
|
92
component/controller/cert.go
Normal file
92
component/controller/cert.go
Normal file
@ -0,0 +1,92 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"crypto/x509/pkix"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type certServe struct {
|
||||
baseController
|
||||
cg *cert.X509Generator
|
||||
}
|
||||
|
||||
func (cs *certServe) Init(rootCert []byte, rootKey []byte) error {
|
||||
cs.cg = cert.NewX509Generator(pkix.Name{
|
||||
Country: []string{"cn"},
|
||||
Organization: []string{"ehang"},
|
||||
OrganizationalUnit: []string{"nps"},
|
||||
Province: []string{"beijing"},
|
||||
CommonName: "nps",
|
||||
Locality: []string{"beijing"},
|
||||
})
|
||||
return cs.cg.InitRootCa(rootCert, rootKey)
|
||||
}
|
||||
|
||||
type certInfo struct {
|
||||
Name string `json:"name"`
|
||||
Uuid string `json:"uuid"`
|
||||
CertType string `json:"cert_type"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
Sn string `json:"sn"`
|
||||
Remark string `json:"remark"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// Create
|
||||
// Cert type root|bridge|server|client|secret
|
||||
func (cs *certServe) Create(c *gin.Context) {
|
||||
var ci certInfo
|
||||
err := c.BindJSON(&ci)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
crt, key, err := cs.cg.CreateCert(fmt.Sprintf("%s.nps.ehang.io", ci.CertType))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
sn, err := cert.GetCertSnFromEncode(crt)
|
||||
ci.Cert, ci.Key, ci.Sn, ci.Uuid = string(crt), string(key), sn, uuid.NewV4().String()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(ci)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
err = cs.db.Insert("cert", ci.Uuid, string(b))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "ok"})
|
||||
}
|
||||
|
||||
func (cs *certServe) Update(c *gin.Context) {
|
||||
var ci certInfo
|
||||
err := c.BindJSON(&ci)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(ci)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
err = cs.db.Update(cs.tableName, ci.Uuid, string(b))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "ok"})
|
||||
}
|
50
component/controller/config.go
Normal file
50
component/controller/config.go
Normal file
@ -0,0 +1,50 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"ehang.io/nps/db"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type configServer struct {
|
||||
db db.Db
|
||||
}
|
||||
|
||||
func (cs *configServer) ChangeSystemConfig(c *gin.Context) {
|
||||
type config struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
NewUsername string `json:"new_username"`
|
||||
}
|
||||
var cfg config
|
||||
err := c.BindJSON(&cfg)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
oldPassword, err := cs.db.GetConfig("admin_pass")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
fmt.Println(cfg, oldPassword)
|
||||
if cfg.OldPassword != oldPassword {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": "old password does not match"})
|
||||
return
|
||||
}
|
||||
if err := cs.db.SetConfig("admin_pass", cfg.NewPassword); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
if cfg.NewUsername != "" {
|
||||
if err := cs.db.SetConfig("admin_pass", cfg.NewUsername); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
101
component/controller/controller.go
Normal file
101
component/controller/controller.go
Normal file
@ -0,0 +1,101 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"ehang.io/nps/db"
|
||||
"ehang.io/nps/lib/logger"
|
||||
jwt "github.com/appleboy/gin-jwt/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func StartController(ln net.Listener, db db.Db, rootCert []byte, rootKey []byte, staticRootPath string, pagePath string) error {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
cfgServer := &configServer{db: db}
|
||||
crtServer := &certServe{baseController: baseController{db: db, tableName: "cert"}}
|
||||
err := crtServer.Init(rootCert, rootKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleServer := &ruleServer{baseController: baseController{db: db, tableName: "rule"}}
|
||||
|
||||
authMiddleware, err := newAuthMiddleware(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router := gin.New()
|
||||
router.Use(CORSMiddleware(), gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
logger.Debug("http request",
|
||||
zap.String("client_ip", param.ClientIP),
|
||||
zap.String("method", param.Method),
|
||||
zap.String("path", param.Path),
|
||||
zap.String("proto", param.Request.Proto),
|
||||
zap.Duration("latency", param.Latency),
|
||||
zap.String("user_agent", param.Request.UserAgent()),
|
||||
zap.String("error_message", param.ErrorMessage),
|
||||
zap.Int("response_code", param.StatusCode),
|
||||
)
|
||||
return ""
|
||||
}))
|
||||
router.POST("/login", authMiddleware.LoginHandler)
|
||||
|
||||
router.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
|
||||
claims := jwt.ExtractClaims(c)
|
||||
logger.Warn("NoRoute", zap.Any("claims", claims))
|
||||
c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
|
||||
})
|
||||
|
||||
auth := router.Group("/auth")
|
||||
auth.Use(authMiddleware.MiddlewareFunc())
|
||||
auth.GET("/refresh_token", authMiddleware.RefreshHandler)
|
||||
auth.GET("/userinfo", userinfo)
|
||||
|
||||
v1 := router.Group("v1")
|
||||
v1.Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
v1.PUT("/config", cfgServer.ChangeSystemConfig)
|
||||
|
||||
v1.GET("/status", status)
|
||||
|
||||
v1.POST("/cert", crtServer.Create)
|
||||
v1.DELETE("/cert", crtServer.Delete)
|
||||
v1.GET("/cert/page", crtServer.Page)
|
||||
v1.PUT("/cert", crtServer.Update)
|
||||
|
||||
v1.POST("/rule", ruleServer.Create)
|
||||
v1.DELETE("/rule", ruleServer.Delete)
|
||||
v1.PUT("/rule", ruleServer.Update)
|
||||
v1.GET("/rule", ruleServer.One)
|
||||
v1.GET("/rule/page", ruleServer.Page)
|
||||
v1.GET("/rule/field", ruleServer.Field)
|
||||
v1.GET("/rule/limiter", ruleServer.Limiter)
|
||||
}
|
||||
router.Static("/static/", staticRootPath)
|
||||
router.Static("/page/", pagePath)
|
||||
|
||||
go storeSystemStatus()
|
||||
|
||||
return router.RunListener(ln)
|
||||
}
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func userinfo(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "application/json; charset=utf-8",
|
||||
[]byte(`{"code":0,"result":{"userId":"1","username":"vben","realName":"Vben Admin","avatar":"https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640","desc":"manager","password":"123456","token":"fakeToken1","homePath":"/dashboard/analysis","roles":[{"roleName":"Super Admin","value":"super"}]},"message":"ok","type":"success"}`))
|
||||
}
|
149
component/controller/controller_test.go
Normal file
149
component/controller/controller_test.go
Normal file
@ -0,0 +1,149 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509/pkix"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/rule"
|
||||
"ehang.io/nps/core/server"
|
||||
"ehang.io/nps/db"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:3500")
|
||||
assert.NoError(t, err)
|
||||
err = os.Remove(filepath.Join(os.TempDir(), "test_control.db"))
|
||||
d := db.NewSqliteDb(filepath.Join(os.TempDir(), "test_control.db"))
|
||||
err = d.Init()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, d.SetConfig("admin_user", "admin"))
|
||||
assert.NoError(t, d.SetConfig("admin_pass", "pass"))
|
||||
cg := cert.NewX509Generator(pkix.Name{
|
||||
Country: []string{"cn"},
|
||||
Organization: []string{"ehang"},
|
||||
OrganizationalUnit: []string{"nps"},
|
||||
Province: []string{"beijing"},
|
||||
CommonName: "nps",
|
||||
Locality: []string{"beijing"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
cert, key, err := cg.CreateRootCa()
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
err = StartController(ln, d, cert, key, "./web/static/", "./web/views/")
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
resp, err := doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/login"), "POST", `{"username": "admin","password": "pass"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(gjson.Parse(resp).Get("code").Int()), 0)
|
||||
|
||||
for i := 0; i < 18; i++ {
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/v1/cert"), "POST", fmt.Sprintf(`{"status":1,"name":"name_%d","cert_type": "client"}`, i))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(gjson.Parse(resp).Get("code").Int()), 0)
|
||||
}
|
||||
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s?page=%d&pageSize=%d", ln.Addr().String(), "/v1/cert/page", 4, 5), "GET", ``)
|
||||
assert.NoError(t, err)
|
||||
now := 2
|
||||
var lastUuid string
|
||||
assert.Equal(t, len(gjson.Parse(resp).Get("result.items").Array()), 3)
|
||||
for _, v := range gjson.Parse(resp).Get("result.items").Array() {
|
||||
assert.Equal(t, v.Get("name").String(), fmt.Sprintf(`name_%d`, now))
|
||||
lastUuid = v.Get("uuid").String()
|
||||
now--
|
||||
}
|
||||
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/v1/cert"), "DELETE", fmt.Sprintf(`{"uuid":"%s"}`, lastUuid))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(gjson.Parse(resp).Get("code").Int()), 0)
|
||||
|
||||
s := &server.TcpServer{ServerAddr: "127.0.0.1:0"}
|
||||
h := &handler.DefaultHandler{}
|
||||
p := &process.DefaultProcess{}
|
||||
a := &action.LocalAction{}
|
||||
rj := &rule.JsonRule{
|
||||
Name: "test",
|
||||
Status: 1,
|
||||
Server: rule.JsonData{ObjType: s.GetName(), ObjData: getJson(t, s)},
|
||||
Handler: rule.JsonData{ObjType: h.GetName(), ObjData: getJson(t, h)},
|
||||
Process: rule.JsonData{ObjType: p.GetName(), ObjData: getJson(t, p)},
|
||||
Action: rule.JsonData{ObjType: a.GetName(), ObjData: getJson(t, a)},
|
||||
Limiters: []rule.JsonData{},
|
||||
}
|
||||
js := getJson(t, rj)
|
||||
for i := 0; i < 18; i++ {
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/v1/rule"), "POST", js)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(gjson.Parse(resp).Get("code").Int()), 0)
|
||||
}
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s?page=%d&pageSize=%d", ln.Addr().String(), "/v1/rule/page", 1, 10), "GET", ``)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(gjson.Parse(resp).Get("result.total").Int()), 18)
|
||||
|
||||
uuid := gjson.Parse(resp).Get("result.items").Array()[0].Get("uuid").String()
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/v1/rule"), "GET", fmt.Sprintf(`{"uuid": "%s"}`, uuid))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, gjson.Parse(resp).Get("result.uuid").String(), uuid)
|
||||
|
||||
rj.Uuid = uuid
|
||||
resp, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/v1/rule"), "PUT", getJson(t, rj))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(gjson.Parse(resp).Get("code").Int()), 0)
|
||||
time.Sleep(time.Minute * 600)
|
||||
}
|
||||
|
||||
func getJson(t *testing.T, i interface{}) string {
|
||||
b, err := json.Marshal(i)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, string(b))
|
||||
return string(b)
|
||||
}
|
||||
|
||||
var client *http.Client
|
||||
var once sync.Once
|
||||
var cookies []*http.Cookie
|
||||
|
||||
func doRequest(url string, method string, body string) (string, error) {
|
||||
once.Do(func() {
|
||||
client = &http.Client{}
|
||||
})
|
||||
payload := bytes.NewBufferString(body)
|
||||
req, err := http.NewRequest(method, url, payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
for _, c := range cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(res.Body)
|
||||
if len(res.Cookies()) > 0 {
|
||||
cookies = res.Cookies()
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return string(b), errors.New("bad doRequest")
|
||||
}
|
||||
return string(b), err
|
||||
}
|
105
component/controller/jwt.go
Normal file
105
component/controller/jwt.go
Normal file
@ -0,0 +1,105 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"ehang.io/nps/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
jwt "github.com/appleboy/gin-jwt/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type login struct {
|
||||
Username string `form:"username" json:"username" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
var identityKey = "id"
|
||||
|
||||
type User struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
func newAuthMiddleware(db db.Db) (authMiddleware *jwt.GinJWTMiddleware, err error) {
|
||||
authMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
|
||||
Realm: "nps",
|
||||
Key: []byte("secret key"),
|
||||
Timeout: time.Hour * 24,
|
||||
MaxRefresh: time.Hour * 72,
|
||||
IdentityKey: identityKey,
|
||||
SendCookie: true,
|
||||
LoginResponse: func(c *gin.Context, code int, message string, time time.Time) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"result": gin.H{
|
||||
"token": message,
|
||||
},
|
||||
"message": "ok",
|
||||
})
|
||||
},
|
||||
PayloadFunc: func(data interface{}) jwt.MapClaims {
|
||||
if v, ok := data.(*User); ok {
|
||||
return jwt.MapClaims{
|
||||
identityKey: v.UserName,
|
||||
}
|
||||
}
|
||||
return jwt.MapClaims{}
|
||||
},
|
||||
IdentityHandler: func(c *gin.Context) interface{} {
|
||||
claims := jwt.ExtractClaims(c)
|
||||
return &User{
|
||||
UserName: claims[identityKey].(string),
|
||||
}
|
||||
},
|
||||
Authenticator: func(c *gin.Context) (interface{}, error) {
|
||||
var loginVals login
|
||||
if err := c.ShouldBind(&loginVals); err != nil {
|
||||
return "", jwt.ErrMissingLoginValues
|
||||
}
|
||||
userID := loginVals.Username
|
||||
password := loginVals.Password
|
||||
adminUser, err := db.GetConfig("admin_user")
|
||||
if err != nil {
|
||||
return "", jwt.ErrFailedAuthentication
|
||||
}
|
||||
adminPass, err := db.GetConfig("admin_pass")
|
||||
if err != nil {
|
||||
return "", jwt.ErrFailedAuthentication
|
||||
}
|
||||
if userID == adminUser && password == adminPass {
|
||||
return &User{
|
||||
UserName: userID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, jwt.ErrFailedAuthentication
|
||||
},
|
||||
Authorizator: func(data interface{}, c *gin.Context) bool {
|
||||
adminUser, err := db.GetConfig("admin_user")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if v, ok := data.(*User); ok && v.UserName ==adminUser {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
Unauthorized: func(c *gin.Context, code int, message string) {
|
||||
c.JSON(code, gin.H{
|
||||
"code": code,
|
||||
"message": message,
|
||||
})
|
||||
},
|
||||
TokenLookup: "header: Authorization, query: token, cookie: jwt",
|
||||
TokenHeadName: "Bearer",
|
||||
TimeFunc: time.Now,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = authMiddleware.MiddlewareInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
151
component/controller/rule.go
Normal file
151
component/controller/rule.go
Normal file
@ -0,0 +1,151 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/rule"
|
||||
"ehang.io/nps/db"
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type baseController struct {
|
||||
db db.Db
|
||||
tableName string
|
||||
}
|
||||
|
||||
func (bc *baseController) Page(c *gin.Context) {
|
||||
page, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
pageSize, err := strconv.Atoi(c.Query("pageSize"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
dataArr, err := bc.db.QueryPage(bc.tableName, pageSize, (page-1)*pageSize, c.Query("key"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
list := make([]map[string]interface{}, 0)
|
||||
for _, s := range dataArr {
|
||||
dd := make(map[string]interface{}, 0)
|
||||
err = json.Unmarshal([]byte(s), &dd)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
list = append(list, dd)
|
||||
}
|
||||
n, err := bc.db.Count(bc.tableName, c.Query("key"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"result": gin.H{
|
||||
"items": list,
|
||||
"total": n,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (bc *baseController) Delete(c *gin.Context) {
|
||||
type uid struct {
|
||||
Uuid string
|
||||
}
|
||||
var js uid
|
||||
err := c.BindJSON(&js)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
err = bc.db.Delete(bc.tableName, js.Uuid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "ok"})
|
||||
}
|
||||
|
||||
type ruleServer struct {
|
||||
baseController
|
||||
}
|
||||
|
||||
func (rs *ruleServer) Create(c *gin.Context) {
|
||||
var jr rule.JsonRule
|
||||
err := c.BindJSON(&jr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
jr.Uuid = uuid.NewV4().String()
|
||||
b, err := json.Marshal(jr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
err = rs.db.Insert(rs.tableName, jr.Uuid, string(b))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "ok"})
|
||||
}
|
||||
|
||||
func (rs *ruleServer) Update(c *gin.Context) {
|
||||
var jr rule.JsonRule
|
||||
err := c.BindJSON(&jr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(jr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
err = rs.db.Update(rs.tableName, jr.Uuid, string(b))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "ok"})
|
||||
}
|
||||
|
||||
func (rs *ruleServer) One(c *gin.Context) {
|
||||
var js map[string]string
|
||||
err := c.BindJSON(&js)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
s, err := rs.db.QueryOne("rule", js["uuid"])
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
var r rule.JsonRule
|
||||
err = json.Unmarshal([]byte(s), &r)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "result": r, "message": "ok"})
|
||||
}
|
||||
|
||||
func (rs *ruleServer) Field(c *gin.Context) {
|
||||
chains := rule.GetChains()
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "result": chains, "message": "ok"})
|
||||
}
|
||||
|
||||
func (rs *ruleServer) Limiter(c *gin.Context) {
|
||||
chains := rule.GetLimiters()
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "result": chains, "message": "ok"})
|
||||
}
|
152
component/controller/status.go
Normal file
152
component/controller/status.go
Normal file
@ -0,0 +1,152 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/load"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/net"
|
||||
"math"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
tData = list.New()
|
||||
)
|
||||
|
||||
type timeData struct {
|
||||
now time.Time
|
||||
cpuData float64
|
||||
load1Data float64
|
||||
load5Data float64
|
||||
load15Data float64
|
||||
swapData float64
|
||||
virtualData float64
|
||||
bandwidthRecvData float64
|
||||
bandwidthSendData float64
|
||||
tcpConnNumData float64
|
||||
udpConnNumData float64
|
||||
diskData float64
|
||||
}
|
||||
|
||||
type dataAddr []float64
|
||||
|
||||
func status(c *gin.Context) {
|
||||
timeArr := make([]string, 0)
|
||||
dataMap := make(map[string][]float64, 0)
|
||||
dataMap["cpu"] = make([]float64, 0)
|
||||
dataMap["load1"] = make([]float64, 0)
|
||||
dataMap["load5"] = make([]float64, 0)
|
||||
dataMap["load15"] = make([]float64, 0)
|
||||
dataMap["swap"] = make([]float64, 0)
|
||||
dataMap["virtual"] = make([]float64, 0)
|
||||
dataMap["bandwidthRecvData"] = make([]float64, 0)
|
||||
dataMap["bandwidthSendData"] = make([]float64, 0)
|
||||
dataMap["tcpConnNumData"] = make([]float64, 0)
|
||||
dataMap["udpConnNumData"] = make([]float64, 0)
|
||||
dataMap["disk"] = make([]float64, 0)
|
||||
now := tData.Front()
|
||||
for {
|
||||
if now == nil {
|
||||
break
|
||||
}
|
||||
data := now.Value.(*timeData)
|
||||
timeArr = append(timeArr, data.now.Format("01-02 15:04"))
|
||||
dataMap["cpu"] = append(dataMap["cpu"], data.cpuData)
|
||||
dataMap["load1"] = append(dataMap["load1"], data.load15Data)
|
||||
dataMap["load5"] = append(dataMap["load5"], data.load5Data)
|
||||
dataMap["load15"] = append(dataMap["load15"], data.load15Data)
|
||||
dataMap["swap"] = append(dataMap["swap"], data.swapData)
|
||||
dataMap["virtual"] = append(dataMap["virtual"], data.virtualData)
|
||||
dataMap["bandwidthSend"] = append(dataMap["bandwidthSend"], data.bandwidthRecvData)
|
||||
dataMap["bandwidthRecv"] = append(dataMap["bandwidthRecv"], data.bandwidthSendData)
|
||||
dataMap["tcp"] = append(dataMap["v"], data.tcpConnNumData)
|
||||
dataMap["udp"] = append(dataMap["udp"], data.udpConnNumData)
|
||||
dataMap["disk"] = append(dataMap["disk"], data.diskData)
|
||||
now = now.Next()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"result": gin.H{
|
||||
"time": timeArr,
|
||||
"data": dataMap,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func storeSystemStatus() {
|
||||
path := "/"
|
||||
if runtime.GOOS == "windows" {
|
||||
path = "C:"
|
||||
}
|
||||
|
||||
for range time.NewTicker(time.Second).C {
|
||||
td := &timeData{now: time.Now()}
|
||||
checkListLen(tData)
|
||||
cpuPercent, err := cpu.Percent(0, true)
|
||||
if err == nil {
|
||||
var cpuAll float64
|
||||
for _, v := range cpuPercent {
|
||||
cpuAll += v
|
||||
}
|
||||
td.cpuData = float64(len(cpuPercent))
|
||||
}
|
||||
|
||||
loads, err := load.Avg()
|
||||
if err == nil {
|
||||
td.load1Data = loads.Load1
|
||||
td.load1Data = loads.Load5
|
||||
td.load15Data = loads.Load15
|
||||
}
|
||||
|
||||
swap, err := mem.SwapMemory()
|
||||
if err == nil {
|
||||
td.swapData = math.Round(swap.UsedPercent)
|
||||
}
|
||||
vir, err := mem.VirtualMemory()
|
||||
if err == nil {
|
||||
td.virtualData = math.Round(vir.UsedPercent)
|
||||
}
|
||||
io1, err := net.IOCounters(false)
|
||||
if err == nil {
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
io2, err := net.IOCounters(false)
|
||||
if err == nil && len(io2) > 0 && len(io1) > 0 {
|
||||
td.bandwidthRecvData = float64((io2[0].BytesRecv-io1[0].BytesRecv)*2) / 1024 / 1024
|
||||
td.bandwidthSendData = float64((io2[0].BytesSent-io1[0].BytesSent)*2) / 1024 / 1024
|
||||
}
|
||||
}
|
||||
conn, err := net.ProtoCounters(nil)
|
||||
if err == nil {
|
||||
for _, v := range conn {
|
||||
if v.Protocol == "tcp" {
|
||||
td.tcpConnNumData = float64(v.Stats["CurrEstab"])
|
||||
}
|
||||
if v.Protocol == "udp" {
|
||||
td.udpConnNumData = float64(v.Stats["CurrEstab"])
|
||||
}
|
||||
}
|
||||
}
|
||||
usage, err := disk.Usage(path)
|
||||
if err == nil {
|
||||
td.diskData = math.Round(usage.UsedPercent)
|
||||
}
|
||||
tData.PushBack(td)
|
||||
}
|
||||
}
|
||||
|
||||
func checkListLen(lists ...*list.List) {
|
||||
for _, l := range lists {
|
||||
if l.Len() > 4320 {
|
||||
if first := l.Front(); first != nil {
|
||||
l.Remove(first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
component/service/service.go
Normal file
11
component/service/service.go
Normal file
@ -0,0 +1,11 @@
|
||||
package service
|
||||
|
||||
import "net"
|
||||
|
||||
type HttpService struct {
|
||||
ln net.Listener
|
||||
}
|
||||
|
||||
func NewHttpService(ln net.Listener) *HttpService {
|
||||
return &HttpService{ln: ln}
|
||||
}
|
110
core/action/action.go
Normal file
110
core/action/action.go
Normal file
@ -0,0 +1,110 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/lib/pool"
|
||||
"errors"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bp = pool.NewBufferPool(MaxReadSize)
|
||||
|
||||
const MaxReadSize = 32 * 1024
|
||||
|
||||
var (
|
||||
_ Action = (*AdminAction)(nil)
|
||||
_ Action = (*BridgeAction)(nil)
|
||||
_ Action = (*LocalAction)(nil)
|
||||
_ Action = (*NpcAction)(nil)
|
||||
)
|
||||
|
||||
type Action interface {
|
||||
GetName() string
|
||||
GetZhName() string
|
||||
Init() error
|
||||
RunConnWithAddr(net.Conn, string) error
|
||||
RunConn(net.Conn) error
|
||||
GetServeConnWithAddr(string) (net.Conn, error)
|
||||
GetServerConn() (net.Conn, error)
|
||||
CanServe() bool
|
||||
RunPacketConn(conn net.PacketConn) error
|
||||
}
|
||||
|
||||
type DefaultAction struct {
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) GetName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) GetZhName() string {
|
||||
return "默认"
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) RunConn(clientConn net.Conn) error {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) CanServe() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) RunPacketConn(conn net.PacketConn) error {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) GetServerConn() (net.Conn, error) {
|
||||
return nil, errors.New("can not get component connection")
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) GetServeConnWithAddr(addr string) (net.Conn, error) {
|
||||
return nil, errors.New("can not get component connection")
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) startCopy(c1 net.Conn, c2 net.Conn) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
err := pool.CopyConnGoroutinePool.Invoke(&pool.CopyConnGpParams{
|
||||
Reader: c2,
|
||||
Writer: c1,
|
||||
Wg: &wg,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Invoke goroutine failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
buf := bp.Get()
|
||||
_, _ = common.CopyBuffer(c2, c1, buf)
|
||||
bp.Put(buf)
|
||||
if v, ok := c1.(*net.TCPConn); ok {
|
||||
_ = v.CloseRead()
|
||||
}
|
||||
if v, ok := c2.(*net.TCPConn); ok {
|
||||
_ = v.CloseWrite()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (ba *DefaultAction) startCopyPacketConn(p1 net.PacketConn, p2 net.PacketConn) error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
_ = pool.CopyPacketGoroutinePool.Invoke(&pool.CopyPacketGpParams{
|
||||
RPacket: p1,
|
||||
WPacket: p2,
|
||||
Wg: &wg,
|
||||
})
|
||||
_ = pool.CopyPacketGoroutinePool.Invoke(&pool.CopyPacketGpParams{
|
||||
RPacket: p2,
|
||||
WPacket: p1,
|
||||
Wg: &wg,
|
||||
})
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
32
core/action/admin.go
Normal file
32
core/action/admin.go
Normal file
@ -0,0 +1,32 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"net"
|
||||
)
|
||||
|
||||
var adminListener = enet.NewListener()
|
||||
|
||||
func GetAdminListener() net.Listener {
|
||||
return adminListener
|
||||
}
|
||||
|
||||
type AdminAction struct {
|
||||
DefaultAction
|
||||
}
|
||||
|
||||
func (la *AdminAction) GetName() string {
|
||||
return "admin"
|
||||
}
|
||||
|
||||
func (la *AdminAction) GetZhName() string {
|
||||
return "转发到控制台"
|
||||
}
|
||||
|
||||
func (la *AdminAction) RunConn(clientConn net.Conn) error {
|
||||
return adminListener.SendConn(clientConn)
|
||||
}
|
||||
|
||||
func (la *AdminAction) RunConnWithAddr(clientConn net.Conn, addr string) error {
|
||||
return adminListener.SendConn(clientConn)
|
||||
}
|
29
core/action/admin_test.go
Normal file
29
core/action/admin_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdminRunConn(t *testing.T) {
|
||||
ac := &AdminAction{
|
||||
DefaultAction: DefaultAction{},
|
||||
}
|
||||
finish := make(chan struct{}, 0)
|
||||
go func() {
|
||||
_, err := GetAdminListener().Accept()
|
||||
assert.NoError(t, err)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, ac.RunConn(conn))
|
||||
}()
|
||||
_, err = net.Dial("tcp", ln.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
<-finish
|
||||
}
|
61
core/action/bridge.go
Normal file
61
core/action/bridge.go
Normal file
@ -0,0 +1,61 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/pool"
|
||||
"net"
|
||||
)
|
||||
|
||||
var bridgeListener = enet.NewListener()
|
||||
var bridgePacketConn enet.PacketConn
|
||||
var packetBp = pool.NewBufferPool(1500)
|
||||
|
||||
func GetBridgeListener() net.Listener {
|
||||
return bridgeListener
|
||||
}
|
||||
|
||||
func GetBridgePacketConn() net.PacketConn {
|
||||
return bridgePacketConn
|
||||
}
|
||||
|
||||
type BridgeAction struct {
|
||||
DefaultAction
|
||||
WritePacketConn net.PacketConn `json:"-"`
|
||||
}
|
||||
|
||||
func (ba *BridgeAction) GetName() string {
|
||||
return "bridge"
|
||||
}
|
||||
|
||||
func (ba *BridgeAction) GetZhName() string {
|
||||
return "转发到网桥"
|
||||
}
|
||||
|
||||
func (ba *BridgeAction) Init() error {
|
||||
bridgePacketConn = enet.NewReaderPacketConn(ba.WritePacketConn, nil, ba.WritePacketConn.LocalAddr())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ba *BridgeAction) RunConn(clientConn net.Conn) error {
|
||||
return bridgeListener.SendConn(clientConn)
|
||||
}
|
||||
|
||||
func (ba *BridgeAction) RunConnWithAddr(clientConn net.Conn, addr string) error {
|
||||
return bridgeListener.SendConn(clientConn)
|
||||
}
|
||||
|
||||
func (ba *BridgeAction) RunPacketConn(pc net.PacketConn) error {
|
||||
b := packetBp.Get()
|
||||
defer packetBp.Put(b)
|
||||
for {
|
||||
n, addr, err := pc.ReadFrom(b)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = bridgePacketConn.SendPacket(b[:n], addr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
73
core/action/bridge_test.go
Normal file
73
core/action/bridge_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBridgeRunConn(t *testing.T) {
|
||||
packetConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
ac := &BridgeAction{
|
||||
DefaultAction: DefaultAction{},
|
||||
WritePacketConn: packetConn,
|
||||
}
|
||||
finish := make(chan struct{}, 0)
|
||||
go func() {
|
||||
_, err := GetBridgeListener().Accept()
|
||||
assert.NoError(t, err)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, ac.RunConn(conn))
|
||||
}()
|
||||
_, err = net.Dial("tcp", ln.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
<-finish
|
||||
}
|
||||
|
||||
func TestBridgeRunPacket(t *testing.T) {
|
||||
packetConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
ac := &BridgeAction{
|
||||
DefaultAction: DefaultAction{},
|
||||
WritePacketConn: packetConn,
|
||||
}
|
||||
assert.NoError(t, ac.Init())
|
||||
go func() {
|
||||
p := make([]byte, 1024)
|
||||
pc := GetBridgePacketConn()
|
||||
n, addr, err := pc.ReadFrom(p)
|
||||
assert.NoError(t, err)
|
||||
_, err = pc.WriteTo(p[:n], addr)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
go func() {
|
||||
p := make([]byte, 1024)
|
||||
n, addr, err := packetConn.ReadFrom(p)
|
||||
assert.NoError(t, err)
|
||||
bPacketConn := enet.NewReaderPacketConn(packetConn, p[:n], addr)
|
||||
go func() {
|
||||
err = ac.RunPacketConn(bPacketConn)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
err = bPacketConn.SendPacket(p[:n], addr)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
cPacketConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
b := []byte("12345")
|
||||
_, err = cPacketConn.WriteTo(b, packetConn.LocalAddr())
|
||||
assert.NoError(t, err)
|
||||
p := make([]byte, 1024)
|
||||
n, addr, err := cPacketConn.ReadFrom(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, addr.String(), packetConn.LocalAddr().String())
|
||||
assert.Equal(t, p[:n], b)
|
||||
}
|
77
core/action/local.go
Normal file
77
core/action/local.go
Normal file
@ -0,0 +1,77 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/lb"
|
||||
"net"
|
||||
)
|
||||
|
||||
type LocalAction struct {
|
||||
DefaultAction
|
||||
TargetAddr []string `json:"target_addr" placeholder:"1.1.1.1:80\n1.1.1.2:80" zh_name:"目标地址"`
|
||||
UnixSocket bool `json:"unix_sock" placeholder:"" zh_name:"转发到unix socket"`
|
||||
networkTcp string
|
||||
localLb lb.Algo
|
||||
}
|
||||
|
||||
func (la *LocalAction) GetName() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (la *LocalAction) GetZhName() string {
|
||||
return "转发到本地"
|
||||
}
|
||||
|
||||
func (la *LocalAction) Init() error {
|
||||
la.localLb = lb.GetLbAlgo("roundRobin")
|
||||
for _, v := range la.TargetAddr {
|
||||
_ = la.localLb.Append(v)
|
||||
}
|
||||
la.networkTcp = "tcp"
|
||||
if la.UnixSocket {
|
||||
// just support unix
|
||||
la.networkTcp = "unix"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (la *LocalAction) RunConn(clientConn net.Conn) error {
|
||||
serverConn, err := la.GetServerConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
la.startCopy(clientConn, serverConn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (la *LocalAction) RunConnWithAddr(clientConn net.Conn, addr string) error {
|
||||
serverConn, err := la.GetServeConnWithAddr(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
la.startCopy(clientConn, serverConn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (la *LocalAction) CanServe() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (la *LocalAction) GetServerConn() (net.Conn, error) {
|
||||
addr, err := la.localLb.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return la.GetServeConnWithAddr(addr.(string))
|
||||
}
|
||||
|
||||
func (la *LocalAction) GetServeConnWithAddr(addr string) (net.Conn, error) {
|
||||
return net.Dial(la.networkTcp, addr)
|
||||
}
|
||||
|
||||
func (la *LocalAction) RunPacketConn(pc net.PacketConn) error {
|
||||
localPacketConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return la.startCopyPacketConn(pc, localPacketConn)
|
||||
}
|
1
core/action/local_test.go
Normal file
1
core/action/local_test.go
Normal file
@ -0,0 +1 @@
|
||||
package action
|
84
core/action/npc.go
Normal file
84
core/action/npc.go
Normal file
@ -0,0 +1,84 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type NpcAction struct {
|
||||
NpcId string `json:"npc_id" required:"true" placeholder:"npc id" zh_name:"客户端"`
|
||||
BridgeAddr string `json:"bridge_addr" placeholder:"127.0.0.1:8080" zh_name:"网桥地址"`
|
||||
UnixSocket bool `json:"unix_sock" placeholder:"" zh_name:"转发到unix socket"`
|
||||
networkTcp pb.ConnType
|
||||
tlsConfig *tls.Config
|
||||
connRequest *pb.ConnRequest
|
||||
DefaultAction
|
||||
}
|
||||
|
||||
func (na *NpcAction) GetName() string {
|
||||
return "npc"
|
||||
}
|
||||
|
||||
func (na *NpcAction) GetZhName() string {
|
||||
return "转发到客户端"
|
||||
}
|
||||
|
||||
func (na *NpcAction) Init() error {
|
||||
if na.tlsConfig == nil {
|
||||
return errors.New("tls config is nil")
|
||||
}
|
||||
sn, err := cert.GetCertSnFromConfig(na.tlsConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get serial number")
|
||||
}
|
||||
na.connRequest = &pb.ConnRequest{Id: sn}
|
||||
na.networkTcp = pb.ConnType_tcp
|
||||
if na.UnixSocket {
|
||||
// just support unix
|
||||
na.networkTcp = pb.ConnType_unix
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (na *NpcAction) RunConnWithAddr(clientConn net.Conn, addr string) error {
|
||||
serverConn, err := na.GetServeConnWithAddr(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
na.startCopy(clientConn, serverConn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (na *NpcAction) CanServe() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (na *NpcAction) GetServeConnWithAddr(addr string) (net.Conn, error) {
|
||||
return dialBridge(na, na.networkTcp, addr)
|
||||
}
|
||||
|
||||
func (na *NpcAction) RunPacketConn(pc net.PacketConn) error {
|
||||
serverPacketConn, err := dialBridge(na, pb.ConnType_udp, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return na.startCopyPacketConn(pc, enet.NewTcpPacketConn(serverPacketConn))
|
||||
}
|
||||
|
||||
func dialBridge(npc *NpcAction, connType pb.ConnType, addr string) (net.Conn, error) {
|
||||
tlsConn, err := tls.Dial("tcp", npc.BridgeAddr, npc.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "dial bridge tls")
|
||||
}
|
||||
cr := proto.Clone(npc.connRequest).(*pb.ConnRequest)
|
||||
cr.ConnType = &pb.ConnRequest_AppInfo{AppInfo: &pb.AppInfo{ConnType: connType, AppAddr: addr, NpcId: npc.NpcId}}
|
||||
if _, err = pb.WriteMessage(tlsConn, cr); err != nil {
|
||||
return nil, errors.Wrap(err, "write enet request")
|
||||
}
|
||||
return tlsConn, err
|
||||
}
|
1
core/action/npc_test.go
Normal file
1
core/action/npc_test.go
Normal file
@ -0,0 +1 @@
|
||||
package action
|
71
core/handler/default.go
Normal file
71
core/handler/default.go
Normal file
@ -0,0 +1,71 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Handler = (*HttpHandler)(nil)
|
||||
_ Handler = (*HttpsHandler)(nil)
|
||||
_ Handler = (*RdpHandler)(nil)
|
||||
_ Handler = (*RedisHandler)(nil)
|
||||
_ Handler = (*Socks5Handler)(nil)
|
||||
_ Handler = (*TransparentHandler)(nil)
|
||||
_ Handler = (*DefaultHandler)(nil)
|
||||
_ Handler = (*DnsHandler)(nil)
|
||||
_ Handler = (*P2PHandler)(nil)
|
||||
_ Handler = (*QUICHandler)(nil)
|
||||
_ Handler = (*DefaultHandler)(nil)
|
||||
_ Handler = (*Socks5UdpHandler)(nil)
|
||||
)
|
||||
|
||||
type RuleRun interface {
|
||||
RunConn(enet.Conn) (bool, error)
|
||||
RunPacketConn(enet.PacketConn) (bool, error)
|
||||
}
|
||||
|
||||
type DefaultHandler struct {
|
||||
ruleList []RuleRun
|
||||
}
|
||||
|
||||
func NewBaseTcpHandler() *DefaultHandler {
|
||||
return &DefaultHandler{ruleList: make([]RuleRun, 0)}
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) GetName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) GetZhName() string {
|
||||
return "默认"
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) HandleConn(_ []byte, c enet.Conn) (bool, error) {
|
||||
return b.processConn(c)
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) AddRule(r RuleRun) {
|
||||
b.ruleList = append(b.ruleList, r)
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) HandlePacketConn(_ enet.PacketConn) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) processConn(c enet.Conn) (bool, error) {
|
||||
for _, r := range b.ruleList {
|
||||
if ok, err := r.RunConn(c); err != nil || ok {
|
||||
return ok, err
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (b *DefaultHandler) processPacketConn(pc enet.PacketConn) (bool, error) {
|
||||
for _, r := range b.ruleList {
|
||||
if ok, err := r.RunPacketConn(pc); err != nil || ok {
|
||||
return ok, err
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
35
core/handler/dns.go
Normal file
35
core/handler/dns.go
Normal file
@ -0,0 +1,35 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type DnsHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (dh *DnsHandler) GetName() string {
|
||||
return "dns"
|
||||
}
|
||||
|
||||
func (dh *DnsHandler) GetZhName() string {
|
||||
return "dns协议"
|
||||
}
|
||||
|
||||
func (dh *DnsHandler) HandlePacketConn(pc enet.PacketConn) (bool, error) {
|
||||
b, _, err := pc.FirstPacket()
|
||||
if err != nil {
|
||||
logger.Warn("firstPacket error", zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
m := new(dns.Msg)
|
||||
err = m.Unpack(b)
|
||||
if err != nil {
|
||||
logger.Debug("parse dns request error", zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
return dh.processPacketConn(pc)
|
||||
}
|
46
core/handler/dns_test.go
Normal file
46
core/handler/dns_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testRule struct {
|
||||
run bool
|
||||
}
|
||||
|
||||
func (t *testRule) RunConn(c enet.Conn) (bool, error) {
|
||||
t.run = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (t *testRule) RunPacketConn(_ enet.PacketConn) (bool, error) {
|
||||
t.run = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func TestHandleDnsPacket(t *testing.T) {
|
||||
lPacketConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
h := DnsHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn("www.google.com"), dns.TypeA)
|
||||
m.RecursionDesired = true
|
||||
|
||||
b, err := m.Pack()
|
||||
assert.NoError(t, err)
|
||||
pc := enet.NewReaderPacketConn(nil, b, lPacketConn.LocalAddr())
|
||||
|
||||
assert.NoError(t, pc.SendPacket(b, nil))
|
||||
res, err := h.HandlePacketConn(pc)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
}
|
11
core/handler/handler.go
Normal file
11
core/handler/handler.go
Normal file
@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import "ehang.io/nps/lib/enet"
|
||||
|
||||
type Handler interface {
|
||||
GetName() string
|
||||
GetZhName() string
|
||||
AddRule(RuleRun)
|
||||
HandleConn([]byte, enet.Conn) (bool, error)
|
||||
HandlePacketConn(enet.PacketConn) (bool, error)
|
||||
}
|
30
core/handler/http.go
Normal file
30
core/handler/http.go
Normal file
@ -0,0 +1,30 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HttpHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func NewHttpHandler() *HttpHandler {
|
||||
return &HttpHandler{}
|
||||
}
|
||||
|
||||
func (h *HttpHandler) GetName() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (h *HttpHandler) GetZhName() string {
|
||||
return "http协议"
|
||||
}
|
||||
|
||||
func (h *HttpHandler) HandleConn(b []byte, c enet.Conn) (bool, error) {
|
||||
switch string(b[:3]) {
|
||||
case http.MethodGet[:3], http.MethodHead[:3], http.MethodPost[:3], http.MethodPut[:3], http.MethodPatch[:3], http.MethodDelete[:3], http.MethodConnect[:3], http.MethodOptions[:3], http.MethodTrace[:3]:
|
||||
return h.processConn(c)
|
||||
}
|
||||
return false, nil
|
||||
}
|
27
core/handler/http_test.go
Normal file
27
core/handler/http_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleHttpConn(t *testing.T) {
|
||||
|
||||
h := HttpHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
r, err := http.NewRequest("GET", "/", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
b, err := httputil.DumpRequest(r, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res, err := h.HandleConn(b, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
}
|
33
core/handler/https.go
Normal file
33
core/handler/https.go
Normal file
@ -0,0 +1,33 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
const (
|
||||
recordTypeHandshake uint8 = 22
|
||||
typeClientHello uint8 = 1
|
||||
)
|
||||
|
||||
type HttpsHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func NewHttpsHandler() *HttpsHandler {
|
||||
return &HttpsHandler{}
|
||||
}
|
||||
|
||||
func (h *HttpsHandler) GetName() string {
|
||||
return "https"
|
||||
}
|
||||
|
||||
func (h *HttpsHandler) GetZhName() string {
|
||||
return "https协议"
|
||||
}
|
||||
|
||||
func (h *HttpsHandler) HandleConn(b []byte, c enet.Conn) (bool, error) {
|
||||
if b[0] == recordTypeHandshake{
|
||||
return h.processConn(c)
|
||||
}
|
||||
return false, nil
|
||||
}
|
39
core/handler/https_test.go
Normal file
39
core/handler/https_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleHttpsConn(t *testing.T) {
|
||||
h := HttpsHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
finish := make(chan struct{}, 0)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
res, err := h.HandleConn(buf[:n], enet.NewReaderConn(conn))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err = tls.Dial("tcp", ln.Addr().String(), &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
<-finish
|
||||
}
|
32
core/handler/p2p.go
Normal file
32
core/handler/p2p.go
Normal file
@ -0,0 +1,32 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type P2PHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (ph *P2PHandler) GetName() string {
|
||||
return "p2p"
|
||||
}
|
||||
|
||||
func (ph *P2PHandler) GetZhName() string {
|
||||
return "点对点协议"
|
||||
}
|
||||
|
||||
func (ph *P2PHandler) HandlePacketConn(pc enet.PacketConn) (bool, error) {
|
||||
b, _, err := pc.FirstPacket()
|
||||
if err != nil {
|
||||
logger.Warn("firstPacket error", zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
if bytes.HasPrefix(b, []byte("p2p")) {
|
||||
return ph.processPacketConn(pc)
|
||||
}
|
||||
return false, nil
|
||||
}
|
26
core/handler/p2p_test.go
Normal file
26
core/handler/p2p_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleP2PPacket(t *testing.T) {
|
||||
|
||||
h := P2PHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8080")
|
||||
assert.NoError(t, err)
|
||||
pc := enet.NewReaderPacketConn(nil, []byte("p2p xxxx"), addr)
|
||||
|
||||
assert.NoError(t, pc.SendPacket([]byte("p2p xxxx"), nil))
|
||||
|
||||
res, err := h.HandlePacketConn(pc)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
}
|
32
core/handler/quic.go
Normal file
32
core/handler/quic.go
Normal file
@ -0,0 +1,32 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type QUICHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (qh *QUICHandler) GetName() string {
|
||||
return "quic"
|
||||
}
|
||||
|
||||
func (qh *QUICHandler) GetZhName() string {
|
||||
return "quic协议"
|
||||
}
|
||||
|
||||
func (qh *QUICHandler) HandlePacketConn(pc enet.PacketConn) (bool, error) {
|
||||
b, _, err := pc.FirstPacket()
|
||||
if err != nil {
|
||||
logger.Warn("firstPacket error", zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
if len(b) >= 5 && bytes.HasPrefix(b[1:5], []byte{0, 0, 0, 1}) {
|
||||
return qh.processPacketConn(pc)
|
||||
}
|
||||
return false, nil
|
||||
}
|
35
core/handler/quic_test.go
Normal file
35
core/handler/quic_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleQUICPacket(t *testing.T) {
|
||||
h := QUICHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
finish := make(chan struct{}, 0)
|
||||
packetConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
b := make([]byte, 1500)
|
||||
n, addr, err := packetConn.ReadFrom(b)
|
||||
assert.NoError(t, err)
|
||||
pc := enet.NewReaderPacketConn(nil, b[:n], packetConn.LocalAddr())
|
||||
assert.NoError(t, pc.SendPacket(b[:n], addr))
|
||||
res, err := h.HandlePacketConn(pc)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
go quic.DialAddr(packetConn.LocalAddr().String(), &tls.Config{}, nil)
|
||||
<-finish
|
||||
}
|
24
core/handler/rdp.go
Normal file
24
core/handler/rdp.go
Normal file
@ -0,0 +1,24 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
type RdpHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (rh *RdpHandler) GetName() string {
|
||||
return "rdp"
|
||||
}
|
||||
|
||||
func (rh *RdpHandler) GetZhName() string {
|
||||
return "rdp协议"
|
||||
}
|
||||
|
||||
func (rh *RdpHandler) HandleConn(b []byte, c enet.Conn) (bool, error) {
|
||||
if b[0] == 3 && b[1] == 0 {
|
||||
return rh.processConn(c)
|
||||
}
|
||||
return false, nil
|
||||
}
|
37
core/handler/rdp_test.go
Normal file
37
core/handler/rdp_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/icodeface/grdp"
|
||||
"github.com/icodeface/grdp/glog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleRdpConn(t *testing.T) {
|
||||
h := RdpHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
finish := make(chan struct{}, 0)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
res, err := h.HandleConn(buf[:n], enet.NewReaderConn(conn))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
grdp.NewClient(ln.Addr().String(), glog.DEBUG).Login("Administrator", "123456")
|
||||
}()
|
||||
<-finish
|
||||
}
|
22
core/handler/redis.go
Normal file
22
core/handler/redis.go
Normal file
@ -0,0 +1,22 @@
|
||||
package handler
|
||||
|
||||
import "ehang.io/nps/lib/enet"
|
||||
|
||||
type RedisHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (rds *RedisHandler) GetName() string {
|
||||
return "redis"
|
||||
}
|
||||
|
||||
func (rds *RedisHandler) GetZhName() string {
|
||||
return "redis协议"
|
||||
}
|
||||
|
||||
func (rds *RedisHandler) HandleConn(b []byte, c enet.Conn) (bool, error) {
|
||||
if b[0] == 42 && b[1] == 49 && b[2] == 13 {
|
||||
return rds.processConn(c)
|
||||
}
|
||||
return false, nil
|
||||
}
|
40
core/handler/redis_test.go
Normal file
40
core/handler/redis_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleRedisConn(t *testing.T) {
|
||||
h := RedisHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
finish := make(chan struct{}, 0)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
res, err := h.HandleConn(buf[:n], enet.NewReaderConn(conn))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: ln.Addr().String(),
|
||||
})
|
||||
rdb.Ping(context.Background())
|
||||
}()
|
||||
<-finish
|
||||
}
|
22
core/handler/socks5.go
Normal file
22
core/handler/socks5.go
Normal file
@ -0,0 +1,22 @@
|
||||
package handler
|
||||
|
||||
import "ehang.io/nps/lib/enet"
|
||||
|
||||
type Socks5Handler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (sh *Socks5Handler) GetName() string {
|
||||
return "socks5"
|
||||
}
|
||||
|
||||
func (sh *Socks5Handler) GetZhName() string {
|
||||
return "socks5协议"
|
||||
}
|
||||
|
||||
func (sh *Socks5Handler) HandleConn(b []byte, c enet.Conn) (bool, error) {
|
||||
if b[0] == 5 {
|
||||
return sh.processConn(c)
|
||||
}
|
||||
return false, nil
|
||||
}
|
47
core/handler/socks5_test.go
Normal file
47
core/handler/socks5_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleSocks5Conn(t *testing.T) {
|
||||
h := Socks5Handler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
finish := make(chan struct{}, 0)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
res, err := h.HandleConn(buf[:n], enet.NewReaderConn(conn))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("socks5://%s", ln.Addr().String()))
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
_, _ = client.Get("https://google.com/")
|
||||
}()
|
||||
<-finish
|
||||
}
|
26
core/handler/socks5_udp.go
Normal file
26
core/handler/socks5_udp.go
Normal file
@ -0,0 +1,26 @@
|
||||
package handler
|
||||
|
||||
import "ehang.io/nps/lib/enet"
|
||||
|
||||
type Socks5UdpHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (sh *Socks5UdpHandler) GetName() string {
|
||||
return "socks5_udp"
|
||||
}
|
||||
|
||||
func (sh *Socks5UdpHandler) GetZhName() string {
|
||||
return "socks5 udp协议"
|
||||
}
|
||||
|
||||
func (sh *Socks5UdpHandler) HandlePacketConn(pc enet.PacketConn) (bool, error) {
|
||||
b, _, err := pc.FirstPacket()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if b[0] == 0 {
|
||||
return sh.processPacketConn(pc)
|
||||
}
|
||||
return false, nil
|
||||
}
|
44
core/handler/socks5_udp_test.go
Normal file
44
core/handler/socks5_udp_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSocks5Handle(t *testing.T) {
|
||||
h := Socks5UdpHandler{}
|
||||
rule := &testRule{}
|
||||
h.AddRule(rule)
|
||||
|
||||
finish := make(chan struct{}, 0)
|
||||
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
n, addr, err := pc.ReadFrom(buf)
|
||||
assert.NoError(t, err)
|
||||
rPc := enet.NewReaderPacketConn(nil, buf[:n], addr)
|
||||
res, err := h.HandlePacketConn(rPc)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, res)
|
||||
assert.Equal(t, true, rule.run)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
|
||||
data := []byte("test")
|
||||
go func() {
|
||||
cPc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
pAddr, err := common.ParseAddr("8.8.8.8:53")
|
||||
assert.NoError(t, err)
|
||||
b := append([]byte{0, 0, 0}, pAddr...)
|
||||
b = append(b, data...)
|
||||
_, err = cPc.WriteTo(b, pc.LocalAddr())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
<-finish
|
||||
}
|
21
core/handler/transparent.go
Normal file
21
core/handler/transparent.go
Normal file
@ -0,0 +1,21 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
type TransparentHandler struct {
|
||||
DefaultHandler
|
||||
}
|
||||
|
||||
func (ts *TransparentHandler) GetName() string {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
func (ts *TransparentHandler) GetZhName() string {
|
||||
return "linux透明代理协议"
|
||||
}
|
||||
|
||||
func (ts *TransparentHandler) HandleConn(b []byte, c enet.Conn) (bool, error) {
|
||||
return ts.processConn(c)
|
||||
}
|
43
core/limiter/conn_num.go
Normal file
43
core/limiter/conn_num.go
Normal file
@ -0,0 +1,43 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// ConnNumLimiter is used to limit the connection num of a service
|
||||
type ConnNumLimiter struct {
|
||||
baseLimiter
|
||||
nowNum int32
|
||||
MaxConnNum int32 `json:"max_conn_num" required:"true" placeholder:"10" zh_name:"最大连接数"` //0 means not limit
|
||||
}
|
||||
|
||||
func (cl *ConnNumLimiter) GetName() string {
|
||||
return "conn_num"
|
||||
}
|
||||
|
||||
func (cl *ConnNumLimiter) GetZhName() string {
|
||||
return "总连接数限制"
|
||||
}
|
||||
|
||||
// DoLimit return an error if the connection num exceed the maximum
|
||||
func (cl *ConnNumLimiter) DoLimit(c enet.Conn) (enet.Conn, error) {
|
||||
if atomic.AddInt32(&cl.nowNum, 1) > cl.MaxConnNum && cl.MaxConnNum > 0 {
|
||||
atomic.AddInt32(&cl.nowNum, -1)
|
||||
return nil, errors.New("exceed maximum number of connections")
|
||||
}
|
||||
return &connNumConn{nowNum: &cl.nowNum}, nil
|
||||
}
|
||||
|
||||
// connNumConn is an implementation of enet.Conn
|
||||
type connNumConn struct {
|
||||
nowNum *int32
|
||||
enet.Conn
|
||||
}
|
||||
|
||||
// Close decrease the connection num
|
||||
func (cn *connNumConn) Close() error {
|
||||
atomic.AddInt32(cn.nowNum, -1)
|
||||
return cn.Conn.Close()
|
||||
}
|
35
core/limiter/conn_num_test.go
Normal file
35
core/limiter/conn_num_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConnNumLimiter(t *testing.T) {
|
||||
cl := ConnNumLimiter{MaxConnNum: 5}
|
||||
assert.NoError(t, cl.Init())
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
nowNum := 0
|
||||
close := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
nowNum++
|
||||
_, err = cl.DoLimit(enet.NewReaderConn(c))
|
||||
if nowNum > 5 {
|
||||
assert.Error(t, err)
|
||||
close <- struct{}{}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
for i := 6; i > 0; i-- {
|
||||
go net.Dial("tcp", ln.Addr().String())
|
||||
}
|
||||
<-close
|
||||
}
|
87
core/limiter/flow.go
Normal file
87
core/limiter/flow.go
Normal file
@ -0,0 +1,87 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// FlowStore is an interface to store or get the flow now
|
||||
type FlowStore interface {
|
||||
GetOutIn() (uint32, uint32)
|
||||
AddOut(out uint32) uint32
|
||||
AddIn(in uint32) uint32
|
||||
}
|
||||
|
||||
// memStore is an implement of FlowStore
|
||||
type memStore struct {
|
||||
nowOut uint32
|
||||
nowIn uint32
|
||||
}
|
||||
|
||||
// GetOutIn return out and in num 0
|
||||
func (m *memStore) GetOutIn() (uint32, uint32) {
|
||||
return m.nowOut, m.nowIn
|
||||
}
|
||||
|
||||
// AddOut is used to add out now
|
||||
func (m *memStore) AddOut(out uint32) uint32 {
|
||||
return atomic.AddUint32(&m.nowOut, out)
|
||||
}
|
||||
|
||||
// AddIn is used to add in now
|
||||
func (m *memStore) AddIn(in uint32) uint32 {
|
||||
return atomic.AddUint32(&m.nowIn, in)
|
||||
}
|
||||
|
||||
// FlowLimiter is used to limit the flow of a service
|
||||
type FlowLimiter struct {
|
||||
Store FlowStore
|
||||
OutLimit uint32 `json:"out_limit" required:"true" placeholder:"1024(kb)" zh_name:"出口最大流量"` //unit: kb, 0 means not limit
|
||||
InLimit uint32 `json:"in_limit" required:"true" placeholder:"1024(kb)" zh_name:"入口最大流量"` //unit: kb, 0 means not limit
|
||||
}
|
||||
|
||||
func (f *FlowLimiter) GetName() string {
|
||||
return "flow"
|
||||
}
|
||||
|
||||
func (f *FlowLimiter) GetZhName() string {
|
||||
return "流量限制"
|
||||
}
|
||||
|
||||
// DoLimit return a flow limited enet.Conn
|
||||
func (f *FlowLimiter) DoLimit(c enet.Conn) (enet.Conn, error) {
|
||||
return &flowConn{fl: f, Conn: c}, nil
|
||||
}
|
||||
|
||||
// Init is used to set out or in num now
|
||||
func (f *FlowLimiter) Init() error {
|
||||
if f.Store == nil {
|
||||
f.Store = &memStore{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// flowConn is an implement of
|
||||
type flowConn struct {
|
||||
enet.Conn
|
||||
fl *FlowLimiter
|
||||
}
|
||||
|
||||
// Read add the in flow num of the service
|
||||
func (fs *flowConn) Read(b []byte) (n int, err error) {
|
||||
n, err = fs.Conn.Read(b)
|
||||
if fs.fl.InLimit > 0 && fs.fl.Store.AddIn(uint32(n)) > fs.fl.InLimit {
|
||||
err = errors.New("exceed the in flow limit")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write add the out flow num of the service
|
||||
func (fs *flowConn) Write(b []byte) (n int, err error) {
|
||||
n, err = fs.Conn.Write(b)
|
||||
if fs.fl.OutLimit > 0 && fs.fl.Store.AddOut(uint32(n)) > fs.fl.OutLimit {
|
||||
err = errors.New("exceed the out flow limit")
|
||||
}
|
||||
return
|
||||
}
|
59
core/limiter/flow_test.go
Normal file
59
core/limiter/flow_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlowLimiter(t *testing.T) {
|
||||
cl := FlowLimiter{
|
||||
OutLimit: 100,
|
||||
InLimit: 100,
|
||||
}
|
||||
assert.NoError(t, cl.Init())
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
nowBytes := 0
|
||||
close := make(chan struct{})
|
||||
go func() {
|
||||
buf := make([]byte, 10)
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
c, err = cl.DoLimit(enet.NewReaderConn(c))
|
||||
for {
|
||||
n, err := c.Read(buf)
|
||||
nowBytes += n
|
||||
if nowBytes > 100 {
|
||||
assert.Error(t, err)
|
||||
nowBytes = 0
|
||||
for i := 11; i > 0; i-- {
|
||||
n, err = c.Write(bytes.Repeat([]byte{0}, 10))
|
||||
nowBytes += n
|
||||
if nowBytes > 100 {
|
||||
assert.Error(t, err)
|
||||
close <- struct{}{}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
c, err := net.Dial("tcp", ln.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
for i := 11; i > 0; i-- {
|
||||
_, err := c.Write(bytes.Repeat([]byte{0}, 10))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
buf := make([]byte, 10)
|
||||
for i := 11; i > 0; i-- {
|
||||
_, err := c.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
<-close
|
||||
}
|
99
core/limiter/ip_conn_num.go
Normal file
99
core/limiter/ip_conn_num.go
Normal file
@ -0,0 +1,99 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/pkg/errors"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ipNumMap is used to store the connection num of a ip address
|
||||
type ipNumMap struct {
|
||||
m map[string]int32
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// AddOrSet is used to add connection num of a ip address
|
||||
func (i *ipNumMap) AddOrSet(key string) {
|
||||
i.Lock()
|
||||
if v, ok := i.m[key]; ok {
|
||||
i.m[key] = v + 1
|
||||
} else {
|
||||
i.m[key] = 1
|
||||
}
|
||||
i.Unlock()
|
||||
}
|
||||
|
||||
// SubOrDel is used to decrease connection of a ip address
|
||||
func (i *ipNumMap) SubOrDel(key string) {
|
||||
i.Lock()
|
||||
if v, ok := i.m[key]; ok {
|
||||
i.m[key] = v - 1
|
||||
if i.m[key] == 0 {
|
||||
delete(i.m, key)
|
||||
}
|
||||
}
|
||||
i.Unlock()
|
||||
}
|
||||
|
||||
// Get return the connection num of a ip
|
||||
func (i *ipNumMap) Get(key string) int32 {
|
||||
return i.m[key]
|
||||
}
|
||||
|
||||
// IpConnNumLimiter is used to limit the connection num of a service at the same time of same ip
|
||||
type IpConnNumLimiter struct {
|
||||
m *ipNumMap
|
||||
MaxNum int32 `json:"max_num" required:"true" placeholder:"10" zh_name:"单ip最大连接数"`
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (cl *IpConnNumLimiter) GetName() string {
|
||||
return "ip_conn_num"
|
||||
}
|
||||
|
||||
func (cl *IpConnNumLimiter) GetZhName() string {
|
||||
return "单ip连接数限制"
|
||||
}
|
||||
|
||||
// Init the ipNumMap
|
||||
func (cl *IpConnNumLimiter) Init() error {
|
||||
cl.m = &ipNumMap{m: make(map[string]int32)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoLimit reports whether the connection num of the ip exceed the maximum number
|
||||
// If true, return error
|
||||
func (cl *IpConnNumLimiter) DoLimit(c enet.Conn) (enet.Conn, error) {
|
||||
ip, _, err := net.SplitHostPort(c.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return c, errors.Wrap(err, "split ip addr")
|
||||
}
|
||||
if cl.m.Get(ip) >= cl.MaxNum {
|
||||
return c, errors.Errorf("the ip(%s) exceed the maximum number(%d)", ip, cl.MaxNum)
|
||||
}
|
||||
return NewNumConn(c, ip, cl.m), nil
|
||||
}
|
||||
|
||||
// numConn is an implement of enet.Conn
|
||||
type numConn struct {
|
||||
key string
|
||||
m *ipNumMap
|
||||
enet.Conn
|
||||
}
|
||||
|
||||
// NewNumConn return a numConn
|
||||
func NewNumConn(c enet.Conn, key string, m *ipNumMap) *numConn {
|
||||
m.AddOrSet(key)
|
||||
return &numConn{
|
||||
m: m,
|
||||
key: key,
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
// Close is used to decrease the connection num of a ip when connection closing
|
||||
func (n *numConn) Close() error {
|
||||
n.m.SubOrDel(n.key)
|
||||
return n.Conn.Close()
|
||||
}
|
35
core/limiter/ip_conn_num_test.go
Normal file
35
core/limiter/ip_conn_num_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIpConnNumLimiter(t *testing.T) {
|
||||
cl := IpConnNumLimiter{MaxNum: 5}
|
||||
assert.NoError(t, cl.Init())
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
nowNum := 0
|
||||
close := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
nowNum++
|
||||
_, err = cl.DoLimit(enet.NewReaderConn(c))
|
||||
if nowNum > 5 {
|
||||
assert.Error(t, err)
|
||||
close <- struct{}{}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
for i := 6; i > 0; i-- {
|
||||
go net.Dial("tcp", ln.Addr().String())
|
||||
}
|
||||
<-close
|
||||
}
|
26
core/limiter/limiter.go
Normal file
26
core/limiter/limiter.go
Normal file
@ -0,0 +1,26 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Limiter = (*RateLimiter)(nil)
|
||||
_ Limiter = (*ConnNumLimiter)(nil)
|
||||
_ Limiter = (*IpConnNumLimiter)(nil)
|
||||
_ Limiter = (*FlowLimiter)(nil)
|
||||
)
|
||||
|
||||
type Limiter interface {
|
||||
DoLimit(conn enet.Conn) (enet.Conn, error)
|
||||
Init() error
|
||||
GetName() string
|
||||
GetZhName() string
|
||||
}
|
||||
|
||||
type baseLimiter struct {
|
||||
}
|
||||
|
||||
func (bl *baseLimiter) Init() error {
|
||||
return nil
|
||||
}
|
67
core/limiter/rate.go
Normal file
67
core/limiter/rate.go
Normal file
@ -0,0 +1,67 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/rate"
|
||||
)
|
||||
|
||||
// RateLimiter is used to limit the speed of transport
|
||||
type RateLimiter struct {
|
||||
baseLimiter
|
||||
RateLimit int64 `json:"rate_limit" required:"true" placeholder:"10(kb)" zh_name:"最大速度"`
|
||||
rate *rate.Rate
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) GetName() string {
|
||||
return "rate"
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) GetZhName() string {
|
||||
return "带宽限制"
|
||||
}
|
||||
|
||||
// Init the rate controller
|
||||
func (rl *RateLimiter) Init() error {
|
||||
if rl.RateLimit > 0 && rl.rate == nil {
|
||||
rl.rate = rate.NewRate(rl.RateLimit)
|
||||
rl.rate.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoLimit return limited Conn
|
||||
func (rl *RateLimiter) DoLimit(c enet.Conn) (enet.Conn, error) {
|
||||
return NewRateConn(c, rl.rate), nil
|
||||
}
|
||||
|
||||
// rateConn is used to limiter the rate fo connection
|
||||
type rateConn struct {
|
||||
enet.Conn
|
||||
rate *rate.Rate
|
||||
}
|
||||
|
||||
// NewRateConn return limited connection by rate interface
|
||||
func NewRateConn(rc enet.Conn, rate *rate.Rate) enet.Conn {
|
||||
return &rateConn{
|
||||
Conn: rc,
|
||||
rate: rate,
|
||||
}
|
||||
}
|
||||
|
||||
// Read data and remove capacity from rate pool
|
||||
func (s *rateConn) Read(b []byte) (n int, err error) {
|
||||
n, err = s.Conn.Read(b)
|
||||
if s.rate != nil && err == nil {
|
||||
err = s.rate.Get(int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write data and remove capacity from rate pool
|
||||
func (s *rateConn) Write(b []byte) (n int, err error) {
|
||||
n, err = s.Conn.Write(b)
|
||||
if s.rate != nil && err == nil {
|
||||
err = s.rate.Get(int64(n))
|
||||
}
|
||||
return
|
||||
}
|
46
core/limiter/rate_test.go
Normal file
46
core/limiter/rate_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRateLimiter(t *testing.T) {
|
||||
cl := RateLimiter{
|
||||
RateLimit: 100,
|
||||
}
|
||||
assert.NoError(t, cl.Init())
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
nowBytes := 0
|
||||
close := make(chan struct{})
|
||||
go func() {
|
||||
buf := make([]byte, 10)
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
c, err = cl.DoLimit(enet.NewReaderConn(c))
|
||||
go func() {
|
||||
<-time.After(time.Second * 2)
|
||||
if nowBytes > 500 {
|
||||
t.Fail()
|
||||
}
|
||||
close <- struct{}{}
|
||||
}()
|
||||
for {
|
||||
n, err := c.Read(buf)
|
||||
nowBytes += n
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
c, err := net.Dial("tcp", ln.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
for i := 11; i > 0; i-- {
|
||||
_, err := c.Write(bytes.Repeat([]byte{0}, 10000))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
<-close
|
||||
}
|
94
core/process/http_proxy.go
Normal file
94
core/process/http_proxy.go
Normal file
@ -0,0 +1,94 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"encoding/base64"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HttpProxyProcess struct {
|
||||
DefaultProcess
|
||||
BasicAuth map[string]string `json:"basic_auth" placeholder:"username1 password1\nusername2 password2" zh_name:"basic认证"`
|
||||
}
|
||||
|
||||
func (hpp *HttpProxyProcess) GetName() string {
|
||||
return "http_proxy"
|
||||
}
|
||||
|
||||
func (hpp *HttpProxyProcess) GetZhName() string {
|
||||
return "http代理"
|
||||
}
|
||||
|
||||
func (hpp *HttpProxyProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
r, err := http.ReadRequest(bufio.NewReader(c))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "read proxy request")
|
||||
}
|
||||
if len(hpp.BasicAuth) != 0 && !hpp.checkAuth(r) {
|
||||
return true, hpp.response(http.StatusProxyAuthRequired, map[string]string{"Proxy-Authenticate": "Basic realm=" + strconv.Quote("nps")}, c)
|
||||
}
|
||||
if r.Method == "CONNECT" {
|
||||
err = hpp.response(200, map[string]string{}, c)
|
||||
if err != nil {
|
||||
return true, errors.Wrap(err, "http proxy response")
|
||||
}
|
||||
} else if err = c.Reset(0); err != nil {
|
||||
logger.Warn("reset enet.Conn error", zap.Error(err))
|
||||
return true, err
|
||||
}
|
||||
address := r.Host
|
||||
if !strings.Contains(r.Host, ":") {
|
||||
if r.URL.Scheme == "https" {
|
||||
address = r.Host + ":443"
|
||||
} else {
|
||||
address = r.Host + ":80"
|
||||
}
|
||||
}
|
||||
return true, hpp.ac.RunConnWithAddr(c, address)
|
||||
}
|
||||
func (hpp *HttpProxyProcess) response(statusCode int, headers map[string]string, c enet.Conn) error {
|
||||
resp := &http.Response{
|
||||
Status: http.StatusText(statusCode),
|
||||
StatusCode: statusCode,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{},
|
||||
}
|
||||
for k, v := range headers {
|
||||
resp.Header.Set(k, v)
|
||||
}
|
||||
return resp.Write(c)
|
||||
}
|
||||
|
||||
func (hpp *HttpProxyProcess) checkAuth(r *http.Request) 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
|
||||
}
|
||||
for u, p := range hpp.BasicAuth {
|
||||
if pair[0] == u && pair[1] == p {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
93
core/process/http_proxy_test.go
Normal file
93
core/process/http_proxy_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpProxyProcess(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
hsAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
h := HttpProxyProcess{
|
||||
DefaultProcess: DefaultProcess{},
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("http://%s", ln.Addr().String()))
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
resp, err = client.Get(fmt.Sprintf("http://%s/now", hsAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpProxyProcessBasic(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
h := HttpProxyProcess{
|
||||
DefaultProcess: DefaultProcess{},
|
||||
BasicAuth: map[string]string{"aaa": "bbb"},
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("http://%s", ln.Addr().String()))
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.Error(t, err)
|
||||
transport.Proxy = func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("http://%s:%s@%s", "aaa", "bbb", ln.Addr().String()))
|
||||
}
|
||||
|
||||
resp, err = client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
79
core/process/http_serve.go
Normal file
79
core/process/http_serve.go
Normal file
@ -0,0 +1,79 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HttpServeProcess is proxy and modify http request
|
||||
type HttpServeProcess struct {
|
||||
DefaultProcess
|
||||
tls bool
|
||||
Host string `json:"host" required:"true" placeholder:"eg: www.nps.com or *.nps.com" zh_name:"域名"`
|
||||
RouteUrl string `json:"route_url" placeholder:"/api" zh_name:"匹配路径"`
|
||||
HeaderModify map[string]string `json:"header_modify" placeholder:"字段 修改值\nHost www.nps-change.com\nAccept */*" zh_name:"请求头修改"`
|
||||
HostModify string `json:"host_modify" placeholder:"www.nps-changed.com" zh_name:"请求域名"`
|
||||
AddOrigin bool `json:"add_origin" zh_name:"添加来源"`
|
||||
CacheTime int64 `json:"cache_time" placeholder:"600s" zh_name:"缓存时间"`
|
||||
CachePath []string `json:"cache_path" placeholder:".jd\n.css\n.png" zh_name:"缓存路径"`
|
||||
BasicAuth map[string]string `json:"basic_auth" placeholder:"username1 password1\nusername2 password2" zh_name:"basic认证"`
|
||||
httpServe *HttpServe
|
||||
ln *enet.Listener
|
||||
}
|
||||
|
||||
func (hp *HttpServeProcess) GetName() string {
|
||||
return "http_serve"
|
||||
}
|
||||
|
||||
func (hp *HttpServeProcess) GetZhName() string {
|
||||
return "http服务"
|
||||
}
|
||||
|
||||
// Init the action of process
|
||||
func (hp *HttpServeProcess) Init(ac action.Action) error {
|
||||
hp.ac = ac
|
||||
hp.ln = enet.NewListener()
|
||||
hp.httpServe = NewHttpServe(hp.ln, ac)
|
||||
hp.httpServe.SetModify(hp.HeaderModify, hp.HostModify, hp.AddOrigin)
|
||||
if hp.CacheTime > 0 {
|
||||
hp.httpServe.SetCache(hp.CachePath, time.Duration(hp.CacheTime)*time.Second)
|
||||
}
|
||||
if len(hp.BasicAuth) != 0 {
|
||||
hp.httpServe.SetBasicAuth(hp.BasicAuth)
|
||||
}
|
||||
if !hp.tls {
|
||||
go hp.httpServe.Serve()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessConn is used to determine whether to hit the rule
|
||||
// If true, send enet to httpServe
|
||||
func (hp *HttpServeProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
req, err := http.ReadRequest(bufio.NewReader(c))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "read request")
|
||||
}
|
||||
host, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "split host")
|
||||
}
|
||||
if !(common.HostContains(hp.Host, host) && (hp.RouteUrl == "" || strings.HasPrefix(req.URL.Path, hp.RouteUrl))) {
|
||||
logger.Debug("do http proxy failed", zap.String("host", host), zap.String("url", hp.RouteUrl))
|
||||
return false, nil
|
||||
}
|
||||
logger.Debug("do http proxy", zap.String("host", host), zap.String("url", hp.RouteUrl))
|
||||
if err := c.Reset(0); err != nil {
|
||||
return true, errors.Wrap(err, "reset connection data")
|
||||
}
|
||||
return true, hp.ln.SendConn(c)
|
||||
}
|
74
core/process/http_serve_test.go
Normal file
74
core/process/http_serve_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpServeProcess(t *testing.T) {
|
||||
sAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
h := &HttpServeProcess{
|
||||
DefaultProcess: DefaultProcess{},
|
||||
Host: "127.0.0.1",
|
||||
RouteUrl: "",
|
||||
HeaderModify: map[string]string{"modify": "nps"},
|
||||
HostModify: "ehang.io",
|
||||
AddOrigin: true,
|
||||
}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{sAddr},
|
||||
}
|
||||
ac.Init()
|
||||
err = h.Init(ac)
|
||||
assert.NoError(t, err)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
rep, err := doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/header/modify"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "nps", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/host"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ehang.io", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/origin/xff"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/origin/xri"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
|
||||
h.BasicAuth = map[string]string{"aaa": "bbb"}
|
||||
h.Init(ac)
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.Error(t, err)
|
||||
_, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/now"), "aaa", "bbb")
|
||||
assert.NoError(t, err)
|
||||
|
||||
h.BasicAuth = map[string]string{}
|
||||
h.CacheTime = 100
|
||||
h.CachePath = []string{"/now"}
|
||||
h.Init(ac)
|
||||
var time1, time2 string
|
||||
time1, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
time2, err = doRequest(fmt.Sprintf("http://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, time1)
|
||||
assert.Equal(t, time1, time2)
|
||||
|
||||
}
|
36
core/process/https_proxy.go
Normal file
36
core/process/https_proxy.go
Normal file
@ -0,0 +1,36 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
type HttpsProxyProcess struct {
|
||||
CertFile string `json:"cert_file" required:"true" placeholder:"/var/cert/cert.pem" zh_name:"cert文件路径"`
|
||||
KeyFile string `json:"key_file" required:"true" placeholder:"/var/cert/key.pem" zh_name:"key文件路径"`
|
||||
config *tls.Config
|
||||
HttpProxyProcess
|
||||
}
|
||||
|
||||
func (hpp *HttpsProxyProcess) GetName() string {
|
||||
return "https_proxy"
|
||||
}
|
||||
|
||||
func (hpp *HttpsProxyProcess) GetZhName() string {
|
||||
return "https代理"
|
||||
}
|
||||
|
||||
func (hpp *HttpsProxyProcess) Init(ac action.Action) error {
|
||||
cer, err := tls.LoadX509KeyPair(hpp.CertFile, hpp.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hpp.config = &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||
hpp.ac = ac
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hpp *HttpsProxyProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
return hpp.HttpProxyProcess.ProcessConn(enet.NewReaderConn(tls.Server(c, hpp.config)))
|
||||
}
|
120
core/process/https_proxy_test.go
Normal file
120
core/process/https_proxy_test.go
Normal file
@ -0,0 +1,120 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var createCertOnce sync.Once
|
||||
|
||||
func createCertFile(t *testing.T) (string, string) {
|
||||
createCertOnce.Do(func() {
|
||||
g := cert.NewX509Generator(pkix.Name{
|
||||
Country: []string{"CN"},
|
||||
Organization: []string{"Ehang.io"},
|
||||
OrganizationalUnit: []string{"nps"},
|
||||
Province: []string{"Beijing"},
|
||||
CommonName: "nps",
|
||||
Locality: []string{"Beijing"},
|
||||
})
|
||||
cert, key, err := g.CreateRootCa()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "cert.pem"), cert, 0600))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "key.pem"), key, 0600))
|
||||
})
|
||||
return filepath.Join(os.TempDir(), "cert.pem"), filepath.Join(os.TempDir(), "key.pem")
|
||||
}
|
||||
|
||||
func TestHttpsProxyProcess(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
certFilePath, keyFilePath := createCertFile(t)
|
||||
h := HttpsProxyProcess{
|
||||
HttpProxyProcess: HttpProxyProcess{},
|
||||
CertFile: certFilePath,
|
||||
KeyFile: keyFilePath,
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("https://%s", ln.Addr().String()))
|
||||
},
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpsProxyProcessBasic(t *testing.T) {
|
||||
certFilePath, keyFilePath := createCertFile(t)
|
||||
sAddr, err := startHttps(t)
|
||||
h := HttpsProxyProcess{
|
||||
HttpProxyProcess: HttpProxyProcess{
|
||||
BasicAuth: map[string]string{"aaa": "bbb"},
|
||||
},
|
||||
CertFile: certFilePath,
|
||||
KeyFile: keyFilePath,
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("https://%s", ln.Addr().String()))
|
||||
},
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.Error(t, err)
|
||||
transport.Proxy = func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("https://%s:%s@%s", "aaa", "bbb", ln.Addr().String()))
|
||||
}
|
||||
|
||||
resp, err = client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
41
core/process/https_redirect.go
Normal file
41
core/process/https_redirect.go
Normal file
@ -0,0 +1,41 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/cert"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HttpsRedirectProcess is used to forward https request by ClientHelloMsg
|
||||
type HttpsRedirectProcess struct {
|
||||
DefaultProcess
|
||||
Host string `json:"host" required:"true" placeholder:"https.nps.com" zh_name:"域名"`
|
||||
}
|
||||
|
||||
func (hrp *HttpsRedirectProcess) GetName() string {
|
||||
return "https_redirect"
|
||||
}
|
||||
|
||||
func (hrp *HttpsRedirectProcess) GetZhName() string {
|
||||
return "https透传"
|
||||
}
|
||||
|
||||
// ProcessConn is used to determine whether to hit the host rule
|
||||
func (hrp *HttpsRedirectProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
clientMsg := cert.ClientHelloMsg{}
|
||||
buf, err := c.AllBytes()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "get bytes")
|
||||
}
|
||||
if !clientMsg.Unmarshal(buf[5:]) {
|
||||
return false, errors.New("can not unmarshal client hello message")
|
||||
}
|
||||
if common.HostContains(hrp.Host, clientMsg.GetServerName()) {
|
||||
if err = c.Reset(0); err != nil {
|
||||
return false, errors.Wrap(err, "reset reader connection")
|
||||
}
|
||||
return true, errors.Wrap(hrp.ac.RunConn(c), "run action")
|
||||
}
|
||||
return false, nil
|
||||
}
|
43
core/process/https_redirect_test.go
Normal file
43
core/process/https_redirect_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpsRedirectProcess(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
assert.NoError(t, err)
|
||||
h := &HttpsRedirectProcess{
|
||||
Host: "ehang.io",
|
||||
}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{sAddr},
|
||||
}
|
||||
ac.Init()
|
||||
err = h.Init(ac)
|
||||
assert.NoError(t, err)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
_, _ = h.ProcessConn(enet.NewReaderConn(c))
|
||||
_ = c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
_, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.Error(t, err)
|
||||
|
||||
h.Host = "*.github.com"
|
||||
_, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
}
|
51
core/process/https_serve.go
Normal file
51
core/process/https_serve.go
Normal file
@ -0,0 +1,51 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/cert"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type HttpsServeProcess struct {
|
||||
CertFile string `json:"cert_file" required:"true" placeholder:"/var/cert/cert.pem" zh_name:"cert文件路径"`
|
||||
KeyFile string `json:"key_file" required:"true" placeholder:"/var/cert/key.pem" zh_name:"key文件路径"`
|
||||
HttpServeProcess
|
||||
}
|
||||
|
||||
func (hsp *HttpsServeProcess) GetName() string {
|
||||
return "https_serve"
|
||||
}
|
||||
func (hsp *HttpsServeProcess) GetZhName() string {
|
||||
return "https服务"
|
||||
}
|
||||
|
||||
func (hsp *HttpsServeProcess) Init(ac action.Action) error {
|
||||
hsp.tls = true
|
||||
err := hsp.HttpServeProcess.Init(ac)
|
||||
go hsp.httpServe.ServeTLS(hsp.CertFile, hsp.KeyFile)
|
||||
return err
|
||||
}
|
||||
|
||||
func (hsp *HttpsServeProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
clientMsg := cert.ClientHelloMsg{}
|
||||
b, err := c.AllBytes()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "get bytes")
|
||||
}
|
||||
if !clientMsg.Unmarshal(b[5:]) {
|
||||
return false, errors.New("can not unmarshal client hello message")
|
||||
}
|
||||
if common.HostContains(hsp.Host, clientMsg.GetServerName()) {
|
||||
logger.Debug("do https serve failed", zap.String("host", clientMsg.GetServerName()), zap.String("url", hsp.RouteUrl))
|
||||
if err := c.Reset(0); err != nil {
|
||||
return true, errors.Wrap(err, "reset reader connection")
|
||||
}
|
||||
return true, hsp.HttpServeProcess.ln.SendConn(c)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
78
core/process/https_serve_test.go
Normal file
78
core/process/https_serve_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpsProcess(t *testing.T) {
|
||||
certFile, keyFile := createCertFile(t)
|
||||
sAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
h := &HttpsServeProcess{
|
||||
HttpServeProcess: HttpServeProcess{
|
||||
Host: "www.github.com",
|
||||
RouteUrl: "",
|
||||
HeaderModify: map[string]string{"modify": "nps"},
|
||||
HostModify: "ehang.io",
|
||||
AddOrigin: true,
|
||||
},
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{sAddr},
|
||||
}
|
||||
ac.Init()
|
||||
err = h.Init(ac)
|
||||
assert.NoError(t, err)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
rep, err := doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/header/modify"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "nps", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/host"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ehang.io", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/origin/xff"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/origin/xri"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
|
||||
h.BasicAuth = map[string]string{"aaa": "bbb"}
|
||||
assert.NoError(t, h.Init(ac))
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.Error(t, err)
|
||||
_, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"), "aaa", "bbb")
|
||||
assert.NoError(t, err)
|
||||
|
||||
h.BasicAuth = map[string]string{}
|
||||
h.CacheTime = 100
|
||||
h.CachePath = []string{"/now"}
|
||||
assert.NoError(t, h.Init(ac))
|
||||
var time1, time2 string
|
||||
time1, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
time2, err = doRequest(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, time1)
|
||||
assert.Equal(t, time1, time2)
|
||||
|
||||
}
|
45
core/process/pb_app.go
Normal file
45
core/process/pb_app.go
Normal file
@ -0,0 +1,45 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PbAppProcessor struct {
|
||||
DefaultProcess
|
||||
}
|
||||
|
||||
func (pp *PbAppProcessor) GetName() string {
|
||||
return "pb_app"
|
||||
}
|
||||
|
||||
func (pp *PbAppProcessor) ProcessConn(c enet.Conn) (bool, error) {
|
||||
m := &pb.ClientRequest{}
|
||||
n, err := pb.ReadMessage(c, m)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
if _, ok := m.ConnType.(*pb.ClientRequest_AppInfo); !ok {
|
||||
return false, nil
|
||||
}
|
||||
if err := c.Reset(n + 4); err != nil {
|
||||
return true, errors.Wrap(err, "reset connection data")
|
||||
}
|
||||
switch m.GetAppInfo().GetConnType() {
|
||||
case pb.ConnType_udp:
|
||||
return true, pp.RunUdp(c)
|
||||
case pb.ConnType_tcp:
|
||||
return true, pp.ac.RunConnWithAddr(c, m.GetAppInfo().GetAppAddr())
|
||||
case pb.ConnType_unix:
|
||||
ac := &action.LocalAction{TargetAddr: []string{m.GetAppInfo().GetAppAddr()}, UnixSocket: true}
|
||||
_ = ac.Init()
|
||||
return true, ac.RunConn(c)
|
||||
}
|
||||
return true, errors.Errorf("can not support the conn type(%d)", m.GetAppInfo().GetConnType())
|
||||
}
|
||||
|
||||
func (pp *PbAppProcessor) RunUdp(c enet.Conn) error {
|
||||
return pp.ac.RunPacketConn(enet.NewTcpPacketConn(c))
|
||||
}
|
113
core/process/pb_app_test.go
Normal file
113
core/process/pb_app_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestProtobufProcess(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
h := &PbAppProcessor{}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{sAddr},
|
||||
}
|
||||
ac.Init()
|
||||
err = h.Init(ac)
|
||||
assert.NoError(t, err)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
_, _ = h.ProcessConn(enet.NewReaderConn(c))
|
||||
_ = c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
client := http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
MaxIdleConns: 10000,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", ln.Addr().String())
|
||||
_, err = pb.WriteMessage(conn, &pb.AppInfo{AppAddr: sAddr})
|
||||
return conn, err
|
||||
},
|
||||
}}
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s%s", ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
|
||||
func TestProtobufUdpProcess(t *testing.T) {
|
||||
finish := make(chan struct{}, 0)
|
||||
lAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
|
||||
udpServer, err := net.ListenUDP("udp", lAddr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
h := &PbAppProcessor{}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{udpServer.LocalAddr().String()},
|
||||
}
|
||||
ac.Init()
|
||||
err = h.Init(ac)
|
||||
assert.NoError(t, err)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
_, _ = h.ProcessConn(enet.NewReaderConn(c))
|
||||
_ = c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
data := []byte{1, 2, 3, 4}
|
||||
dataReturn := []byte{4, 5, 6, 7}
|
||||
conn, err := net.Dial("tcp", ln.Addr().String())
|
||||
_, err = pb.WriteMessage(conn, &pb.AppInfo{AppAddr: udpServer.LocalAddr().String(), ConnType: pb.ConnType_udp})
|
||||
|
||||
go func() {
|
||||
b := make([]byte, 1024)
|
||||
n, addr, err := udpServer.ReadFrom(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, b[:n], data)
|
||||
|
||||
_, err = udpServer.WriteTo(dataReturn, addr)
|
||||
assert.NoError(t, err)
|
||||
finish <- struct{}{}
|
||||
}()
|
||||
|
||||
c := enet.NewTcpPacketConn(conn)
|
||||
_, err = c.WriteTo(data, udpServer.LocalAddr())
|
||||
assert.NoError(t, err)
|
||||
|
||||
<-finish
|
||||
b := make([]byte, 1024)
|
||||
n, addr, err := c.ReadFrom(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dataReturn, b[:n])
|
||||
assert.Equal(t, addr.String(), udpServer.LocalAddr().String())
|
||||
}
|
29
core/process/pb_ping.go
Normal file
29
core/process/pb_ping.go
Normal file
@ -0,0 +1,29 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PbPingProcessor struct {
|
||||
DefaultProcess
|
||||
}
|
||||
|
||||
func (pp *PbPingProcessor) GetName() string {
|
||||
return "pb_ping"
|
||||
}
|
||||
|
||||
func (pp *PbPingProcessor) ProcessConn(c enet.Conn) (bool, error) {
|
||||
m := &pb.ClientRequest{}
|
||||
_, err := pb.ReadMessage(c, m)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
if _, ok := m.ConnType.(*pb.ClientRequest_Ping); !ok {
|
||||
return false, nil
|
||||
}
|
||||
m.GetPing().Now = time.Now().String()
|
||||
_, err = pb.WriteMessage(c, m)
|
||||
return true, err
|
||||
}
|
43
core/process/pb_ping_test.go
Normal file
43
core/process/pb_ping_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPbPingProcess(t *testing.T) {
|
||||
|
||||
h := &PbPingProcessor{}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{},
|
||||
}
|
||||
ac.Init()
|
||||
err = h.Init(ac)
|
||||
assert.NoError(t, err)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
_, _ = h.ProcessConn(enet.NewReaderConn(c))
|
||||
_ = c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
conn, err := net.Dial("tcp", ln.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
_, err = pb.WriteMessage(conn, &pb.Ping{Now: time.Now().String()})
|
||||
assert.NoError(t, err)
|
||||
m := &pb.Ping{}
|
||||
_, err = pb.ReadMessage(conn, m)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, m.Now)
|
||||
}
|
51
core/process/process.go
Normal file
51
core/process/process.go
Normal file
@ -0,0 +1,51 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Process = (*DefaultProcess)(nil)
|
||||
_ Process = (*HttpServeProcess)(nil)
|
||||
_ Process = (*HttpsServeProcess)(nil)
|
||||
_ Process = (*HttpProxyProcess)(nil)
|
||||
_ Process = (*HttpsProxyProcess)(nil)
|
||||
_ Process = (*HttpsRedirectProcess)(nil)
|
||||
_ Process = (*Socks5Process)(nil)
|
||||
_ Process = (*TransparentProcess)(nil)
|
||||
)
|
||||
|
||||
type Process interface {
|
||||
Init(action action.Action) error
|
||||
GetName() string
|
||||
GetZhName() string
|
||||
ProcessConn(enet.Conn) (bool, error)
|
||||
ProcessPacketConn(enet.PacketConn) (bool, error)
|
||||
}
|
||||
|
||||
type DefaultProcess struct {
|
||||
ac action.Action
|
||||
}
|
||||
|
||||
func (bp *DefaultProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
return true, bp.ac.RunConn(c)
|
||||
}
|
||||
|
||||
func (bp *DefaultProcess) GetName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
func (bp *DefaultProcess) GetZhName() string {
|
||||
return "默认"
|
||||
}
|
||||
|
||||
// Init the action of process
|
||||
func (bp *DefaultProcess) Init(ac action.Action) error {
|
||||
bp.ac = ac
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bp *DefaultProcess) ProcessPacketConn(pc enet.PacketConn) (bool, error) {
|
||||
return true, bp.ac.RunPacketConn(pc)
|
||||
}
|
169
core/process/serve.go
Normal file
169
core/process/serve.go
Normal file
@ -0,0 +1,169 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"github.com/gin-contrib/cache"
|
||||
"github.com/gin-contrib/cache/persistence"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HttpServe struct {
|
||||
engine *gin.Engine
|
||||
ln net.Listener
|
||||
ac action.Action
|
||||
httpServe *http.Server
|
||||
cacheStore *persistence.InMemoryStore
|
||||
cacheTime time.Duration
|
||||
cachePath []string
|
||||
headerModify map[string]string
|
||||
hostModify string
|
||||
addOrigin bool
|
||||
reverseProxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func NewHttpServe(ln net.Listener, ac action.Action) *HttpServe {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
hs := &HttpServe{
|
||||
ln: ln,
|
||||
ac: ac,
|
||||
engine: gin.New(),
|
||||
}
|
||||
hs.httpServe = &http.Server{
|
||||
Handler: hs.engine,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
}
|
||||
|
||||
hs.reverseProxy = &httputil.ReverseProxy{
|
||||
Director: func(request *http.Request) {
|
||||
_ = hs.transport(request)
|
||||
hs.doModify(request)
|
||||
},
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: 10000,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return ac.GetServerConn()
|
||||
},
|
||||
},
|
||||
}
|
||||
serverHttp := func(w http.ResponseWriter, r *http.Request) {
|
||||
hs.reverseProxy.ServeHTTP(w, r)
|
||||
}
|
||||
hs.engine.NoRoute(func(c *gin.Context) {
|
||||
cached := false
|
||||
for _, p := range hs.cachePath {
|
||||
if strings.Contains(c.Request.RequestURI, p) {
|
||||
cached = true
|
||||
cache.CachePage(hs.cacheStore, hs.cacheTime, func(c *gin.Context) {
|
||||
serverHttp(c.Writer, c.Request)
|
||||
})(c)
|
||||
}
|
||||
}
|
||||
if !cached {
|
||||
serverHttp(c.Writer, c.Request)
|
||||
}
|
||||
})
|
||||
hs.engine.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
logger.Debug("http request",
|
||||
zap.String("client_ip", param.ClientIP),
|
||||
zap.String("method", param.Method),
|
||||
zap.String("path", param.Path),
|
||||
zap.String("proto", param.Request.Proto),
|
||||
zap.Duration("latency", param.Latency),
|
||||
zap.String("user_agent", param.Request.UserAgent()),
|
||||
zap.String("error_message", param.ErrorMessage),
|
||||
zap.Int("response_code", param.StatusCode),
|
||||
)
|
||||
return ""
|
||||
}))
|
||||
return hs
|
||||
}
|
||||
|
||||
func (hs *HttpServe) transport(req *http.Request) error {
|
||||
ruri := req.URL.RequestURI()
|
||||
req.URL.Scheme = "http"
|
||||
if req.URL.Scheme != "" && req.URL.Opaque == "" {
|
||||
ruri = req.URL.Scheme + "://" + req.Host + ruri
|
||||
} else if req.Method == "CONNECT" && req.URL.Path == "" {
|
||||
// CONNECT requests normally give just the host and port, not a full URL.
|
||||
ruri = req.Host
|
||||
if req.URL.Opaque != "" {
|
||||
ruri = req.URL.Opaque
|
||||
}
|
||||
}
|
||||
req.RequestURI = ""
|
||||
var err error
|
||||
req.URL, err = url.Parse(ruri)
|
||||
return err
|
||||
}
|
||||
|
||||
func (hs *HttpServe) SetBasicAuth(accounts map[string]string) {
|
||||
hs.engine.Use(gin.BasicAuth(accounts), gin.Recovery())
|
||||
}
|
||||
|
||||
func (hs *HttpServe) SetCache(cachePath []string, cacheTime time.Duration) {
|
||||
hs.cachePath = cachePath
|
||||
hs.cacheTime = cacheTime
|
||||
hs.cacheStore = persistence.NewInMemoryStore(cacheTime * time.Second)
|
||||
}
|
||||
|
||||
func (hs *HttpServe) SetModify(headerModify map[string]string, hostModify string, addOrigin bool) {
|
||||
hs.headerModify = headerModify
|
||||
hs.hostModify = hostModify
|
||||
hs.addOrigin = addOrigin
|
||||
return
|
||||
}
|
||||
|
||||
func (hs *HttpServe) Serve() error {
|
||||
return hs.httpServe.Serve(hs.ln)
|
||||
}
|
||||
|
||||
func (hs *HttpServe) ServeTLS(certFile string, keyFile string) error {
|
||||
return hs.httpServe.ServeTLS(hs.ln, certFile, keyFile)
|
||||
}
|
||||
|
||||
// doModify is used to modify http request
|
||||
func (hs *HttpServe) doModify(req *http.Request) {
|
||||
if hs.hostModify != "" {
|
||||
req.Host = hs.hostModify
|
||||
}
|
||||
for k, v := range hs.headerModify {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
addr := strings.Split(req.RemoteAddr, ":")[0]
|
||||
if hs.addOrigin {
|
||||
// XFF is setting in reverseProxy
|
||||
req.Header.Set("X-Real-IP", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
header := w.Header()
|
||||
if val := header["Content-Type"]; len(val) == 0 {
|
||||
header["Content-Type"] = value
|
||||
}
|
||||
}
|
||||
|
||||
type render struct {
|
||||
resp *http.Response
|
||||
}
|
||||
|
||||
func (r *render) Render(writer http.ResponseWriter) error {
|
||||
_, err := io.Copy(writer, r.resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *render) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, []string{r.resp.Header.Get("Content-Type")})
|
||||
}
|
299
core/process/serve_test.go
Normal file
299
core/process/serve_test.go
Normal file
@ -0,0 +1,299 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/core/action"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/websocket"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var startHttpOnce sync.Once
|
||||
var startHttpsOnce sync.Once
|
||||
var handleOnce sync.Once
|
||||
var ln net.Listener
|
||||
var lns net.Listener
|
||||
var err error
|
||||
|
||||
func registerHandle() {
|
||||
handleOnce.Do(func() {
|
||||
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
|
||||
msg := make([]byte, 512)
|
||||
n, err := ws.Read(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ws.Write(msg[:n])
|
||||
}))
|
||||
http.HandleFunc("/now", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(time.Now().String()))
|
||||
})
|
||||
http.HandleFunc("/host", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(r.Host))
|
||||
})
|
||||
http.HandleFunc("/header/modify", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(r.Header.Get("modify")))
|
||||
})
|
||||
http.HandleFunc("/origin/xff", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(r.Header.Get("X-Forwarded-For")))
|
||||
})
|
||||
http.HandleFunc("/origin/xri", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(r.Header.Get("X-Real-IP")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func startHttp(t *testing.T) (string, error) {
|
||||
startHttpOnce.Do(func() {
|
||||
ln, err = net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
registerHandle()
|
||||
go http.Serve(ln, nil)
|
||||
|
||||
})
|
||||
|
||||
return ln.Addr().String(), err
|
||||
}
|
||||
|
||||
func startHttps(t *testing.T) (string, error) {
|
||||
startHttpsOnce.Do(func() {
|
||||
lns, err = net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
registerHandle()
|
||||
certFilePath, keyFilePath := createCertFile(t)
|
||||
go http.ServeTLS(lns, nil, certFilePath, keyFilePath)
|
||||
})
|
||||
|
||||
return lns.Addr().String(), err
|
||||
}
|
||||
|
||||
func doRequest(params ...string) (string, error) {
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest("GET", params[0], nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Connection", "close")
|
||||
if len(params) >= 3 && params[1] != "" {
|
||||
req.SetBasicAuth(params[1], params[2])
|
||||
}
|
||||
if req.URL.Scheme == "https" {
|
||||
client.Transport = &http.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return tls.Dial(network, addr, &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "www.github.com",
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return "0", errors.Errorf("respond error, code %d", resp.StatusCode)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func createHttpServe(serverAddr string) (*HttpServe, error) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ac := &action.LocalAction{
|
||||
DefaultAction: action.DefaultAction{},
|
||||
TargetAddr: []string{serverAddr},
|
||||
}
|
||||
ac.Init()
|
||||
return NewHttpServe(ln, ac), nil
|
||||
}
|
||||
|
||||
func TestHttpServeWebsocket(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
go hs.Serve()
|
||||
|
||||
ws, err := websocket.Dial(fmt.Sprintf("ws://%s/ws", hs.ln.Addr().String()), "", fmt.Sprintf("http://%s/ws", hs.ln.Addr().String()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer ws.Close() //关闭连接
|
||||
|
||||
sendMsg := []byte("nps")
|
||||
_, err = ws.Write(sendMsg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := make([]byte, 512)
|
||||
m, err := ws.Read(msg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, sendMsg, msg[:m])
|
||||
}
|
||||
|
||||
func TestHttpsServeWebsocket(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
cert, key := createCertFile(t)
|
||||
go hs.ServeTLS(cert, key)
|
||||
|
||||
config, err := websocket.NewConfig(fmt.Sprintf("wss://%s/ws", hs.ln.Addr().String()), fmt.Sprintf("https://%s/ws", hs.ln.Addr().String()))
|
||||
assert.NoError(t, err)
|
||||
config.TlsConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
ws, err := websocket.DialConfig(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer ws.Close() //关闭连接
|
||||
|
||||
sendMsg := []byte("nps")
|
||||
_, err = ws.Write(sendMsg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := make([]byte, 512)
|
||||
m, err := ws.Read(msg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, sendMsg, msg[:m])
|
||||
}
|
||||
|
||||
func TestHttpServeModify(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
go hs.Serve()
|
||||
|
||||
hs.SetModify(map[string]string{"modify": "test"}, "ehang.io", true)
|
||||
|
||||
rep, err := doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/header/modify"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/host"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ehang.io", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/origin/xff"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/origin/xri"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
}
|
||||
|
||||
func TestHttpsServeModify(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
cert, key := createCertFile(t)
|
||||
go hs.ServeTLS(cert, key)
|
||||
|
||||
hs.SetModify(map[string]string{"modify": "test"}, "ehang.io", true)
|
||||
|
||||
rep, err := doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/header/modify"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/host"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ehang.io", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/origin/xff"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
|
||||
rep, err = doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/origin/xri"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1", rep)
|
||||
}
|
||||
|
||||
func TestHttpServeCache(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
go hs.Serve()
|
||||
hs.SetCache([]string{"now"}, time.Second*10)
|
||||
|
||||
var time1, time2 string
|
||||
time1, err = doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
time2, err = doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, time1)
|
||||
assert.Equal(t, time1, time2)
|
||||
}
|
||||
|
||||
func TestHttpsServeCache(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
cert, key := createCertFile(t)
|
||||
go hs.ServeTLS(cert, key)
|
||||
hs.SetCache([]string{"now"}, time.Second*10)
|
||||
|
||||
var time1, time2 string
|
||||
time1, err = doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
time2, err = doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/now"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, time1)
|
||||
assert.Equal(t, time1, time2)
|
||||
}
|
||||
|
||||
func TestHttpServeBasicAuth(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
go hs.Serve()
|
||||
hs.SetBasicAuth(map[string]string{"aaa": "bbb"})
|
||||
_, err = doRequest(fmt.Sprintf("http://%s%s", hs.ln.Addr().String(), "/now"), "aaa", "bbb")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHttpsServeBasicAuth(t *testing.T) {
|
||||
serverAddr, err := startHttp(t)
|
||||
assert.NoError(t, err)
|
||||
hs, err := createHttpServe(serverAddr)
|
||||
assert.NoError(t, err)
|
||||
cert, key := createCertFile(t)
|
||||
go hs.ServeTLS(cert, key)
|
||||
|
||||
hs.SetBasicAuth(map[string]string{"aaa": "bbb"})
|
||||
_, err = doRequest(fmt.Sprintf("https://%s%s", hs.ln.Addr().String(), "/now"), "aaa", "bbb")
|
||||
assert.NoError(t, err)
|
||||
}
|
219
core/process/socks5.go
Normal file
219
core/process/socks5.go
Normal file
@ -0,0 +1,219 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"encoding/binary"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/go-cache"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Socks5Process struct {
|
||||
DefaultProcess
|
||||
Accounts map[string]string `json:"accounts" placeholder:"username1 password1\nusername2 password2" zh_name:"授权账号密码"`
|
||||
ServerIp string `json:"server_ip" placeholder:"123.123.123.123" zh_name:"udp连接地址"`
|
||||
ipStore *cache.Cache
|
||||
}
|
||||
|
||||
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 headers5. IPv4 has a 20 byte header, UDP adds an
|
||||
// additional 4 bytes5. This is a total overhead of 24 bytes5. 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)
|
||||
)
|
||||
|
||||
func (s5 *Socks5Process) GetName() string {
|
||||
return "socks5"
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) GetZhName() string {
|
||||
return "socks5代理"
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) Init(ac action.Action) error {
|
||||
s5.ipStore = cache.New(time.Minute, time.Minute*2)
|
||||
return s5.DefaultProcess.Init(ac)
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) ProcessConn(c enet.Conn) (bool, error) {
|
||||
return true, s5.handleConn(c)
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) ProcessPacketConn(pc enet.PacketConn) (bool, error) {
|
||||
ip, _, _ := net.SplitHostPort(pc.LocalAddr().String())
|
||||
if _, ok := s5.ipStore.Get(ip); !ok {
|
||||
return false, nil
|
||||
}
|
||||
_, addr, err := pc.FirstPacket()
|
||||
if err != nil {
|
||||
return false, errors.New("addr not found")
|
||||
}
|
||||
return true, s5.ac.RunPacketConn(enet.NewS5PacketConn(pc, addr))
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) handleConn(c enet.Conn) error {
|
||||
buf := make([]byte, 2)
|
||||
if _, err := io.ReadFull(c, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if version := buf[0]; version != 5 {
|
||||
return errors.New("only support socks5")
|
||||
}
|
||||
nMethods := buf[1]
|
||||
|
||||
methods := make([]byte, nMethods)
|
||||
if l, err := c.Read(methods); l != int(nMethods) || err != nil {
|
||||
return errors.New("wrong method")
|
||||
}
|
||||
|
||||
if len(s5.Accounts) > 0 {
|
||||
buf[1] = UserPassAuth
|
||||
_, err := c.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s5.Auth(c); err != nil {
|
||||
return errors.Wrap(err, "auth failed")
|
||||
}
|
||||
} else {
|
||||
buf[1] = 0
|
||||
_, _ = c.Write(buf)
|
||||
}
|
||||
return s5.handleRequest(c)
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) Auth(c enet.Conn) error {
|
||||
header := []byte{0, 0}
|
||||
if _, err := io.ReadAtLeast(c, header, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
if header[0] != userAuthVersion {
|
||||
return errors.New("auth type not support")
|
||||
}
|
||||
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("the length of password is incorrect")
|
||||
}
|
||||
passLen := int(header[0])
|
||||
pass := make([]byte, passLen)
|
||||
if _, err := io.ReadAtLeast(c, pass, passLen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := s5.Accounts[string(user)]
|
||||
|
||||
if p == "" || string(pass) != p {
|
||||
_, _ = c.Write([]byte{userAuthVersion, authFailure})
|
||||
return errors.New("auth failure")
|
||||
}
|
||||
|
||||
if _, err := c.Write([]byte{userAuthVersion, authSuccess}); err != nil {
|
||||
return errors.Wrap(err, "write auth success")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) handleRequest(c enet.Conn) error {
|
||||
header := make([]byte, 3)
|
||||
|
||||
_, err := io.ReadFull(c, header)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header[1] {
|
||||
case connectMethod:
|
||||
s5.handleConnect(c)
|
||||
case associateMethod:
|
||||
s5.handleUDP(c)
|
||||
default:
|
||||
s5.sendReply(c, commandNotSupported)
|
||||
c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//enet
|
||||
func (s5 *Socks5Process) handleConnect(c enet.Conn) {
|
||||
addr, err := common.ReadAddr(c)
|
||||
if err != nil {
|
||||
s5.sendReply(c, addrTypeNotSupported)
|
||||
logger.Warn("read socks addr error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
s5.sendReply(c, succeeded)
|
||||
_ = s5.ac.RunConnWithAddr(c, addr.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) handleUDP(c net.Conn) {
|
||||
_, err := common.ReadAddr(c)
|
||||
if err != nil {
|
||||
s5.sendReply(c, addrTypeNotSupported)
|
||||
logger.Warn("read socks addr error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
ip, _, _ := net.SplitHostPort(c.RemoteAddr().String())
|
||||
s5.ipStore.Set(ip, true, time.Minute)
|
||||
s5.sendReply(c, succeeded)
|
||||
}
|
||||
|
||||
func (s5 *Socks5Process) sendReply(c net.Conn, rep uint8) {
|
||||
reply := []byte{
|
||||
5,
|
||||
rep,
|
||||
0,
|
||||
1,
|
||||
}
|
||||
|
||||
localHost, localPort, _ := net.SplitHostPort(c.LocalAddr().String())
|
||||
if s5.ServerIp != "" {
|
||||
localHost = s5.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...)
|
||||
_, _ = c.Write(reply)
|
||||
}
|
152
core/process/socks5_test.go
Normal file
152
core/process/socks5_test.go
Normal file
@ -0,0 +1,152 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/lib/common"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/proxy"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSocks5ProxyProcess(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
assert.NoError(t, err)
|
||||
h := Socks5Process{
|
||||
DefaultProcess: DefaultProcess{},
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go h.ProcessConn(enet.NewReaderConn(c))
|
||||
}
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(fmt.Sprintf("socks5://%s", ln.Addr().String()))
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestSocks5ProxyProcessAuth(t *testing.T) {
|
||||
sAddr, err := startHttps(t)
|
||||
h := Socks5Process{
|
||||
DefaultProcess: DefaultProcess{},
|
||||
Accounts: map[string]string{"aaa": "bbb"},
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
_, _ = h.ProcessConn(enet.NewReaderConn(c))
|
||||
_ = c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
auth := proxy.Auth{
|
||||
User: "aaa",
|
||||
Password: "bbb",
|
||||
}
|
||||
|
||||
dialer, err := proxy.SOCKS5("tcp", ln.Addr().String(), nil, proxy.Direct)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tr := &http.Transport{Dial: dialer.Dial, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.Error(t, err)
|
||||
|
||||
dialer, err = proxy.SOCKS5("tcp", ln.Addr().String(), &auth, proxy.Direct)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tr = &http.Transport{Dial: dialer.Dial, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
client = &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
resp, err = client.Get(fmt.Sprintf("https://%s/now", sAddr))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestSocks5ProxyProcessUdp(t *testing.T) {
|
||||
h := Socks5Process{
|
||||
DefaultProcess: DefaultProcess{},
|
||||
}
|
||||
ac := &action.LocalAction{}
|
||||
ac.Init()
|
||||
assert.NoError(t, h.Init(ac))
|
||||
h.ipStore.Set("127.0.0.1", true, time.Minute)
|
||||
|
||||
serverPc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
localPc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
appPc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
data := []byte("test")
|
||||
go func() {
|
||||
p := make([]byte, 1500)
|
||||
n, addr, err := appPc.ReadFrom(p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p[:n], data)
|
||||
_, err = appPc.WriteTo(data, addr)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
go func() {
|
||||
p := make([]byte, 1500)
|
||||
n, addr, err := serverPc.ReadFrom(p)
|
||||
assert.NoError(t, err)
|
||||
pc := enet.NewReaderPacketConn(serverPc, p[:n], addr)
|
||||
err = pc.SendPacket(p[:n], addr)
|
||||
assert.NoError(t, err)
|
||||
b, err := h.ProcessPacketConn(pc)
|
||||
assert.Equal(t, b, true)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
b := []byte{0, 0, 0}
|
||||
pAddr, err := common.ParseAddr(appPc.LocalAddr().String())
|
||||
assert.NoError(t, err)
|
||||
b = append(b, pAddr...)
|
||||
b = append(b, data...)
|
||||
_, err = localPc.WriteTo(b, serverPc.LocalAddr())
|
||||
assert.NoError(t, err)
|
||||
p := make([]byte, 1500)
|
||||
n, _, err := localPc.ReadFrom(p)
|
||||
assert.NoError(t, err)
|
||||
respAddr, err := common.SplitAddr(p[3:])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, respAddr.String(), appPc.LocalAddr().String())
|
||||
assert.Equal(t, p[3+len(respAddr):n], data)
|
||||
}
|
56
core/process/transparent_linux.go
Normal file
56
core/process/transparent_linux.go
Normal file
@ -0,0 +1,56 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const SO_ORIGINAL_DST = 80
|
||||
|
||||
type TransparentProcess struct {
|
||||
DefaultProcess
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) GetName() string {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) GetZhName() string {
|
||||
return "透明代理"
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
addr, err := tp.getAddress(c)
|
||||
if err != nil {
|
||||
logger.Debug("get syscall error", zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
return true, tp.ac.RunConnWithAddr(c, addr)
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) getAddress(conn net.Conn) (string, error) {
|
||||
// TODO: IPV6 support
|
||||
sysrawConn, f := conn.(syscall.Conn)
|
||||
if !f {
|
||||
return "", nil
|
||||
}
|
||||
rawConn, err := sysrawConn.SyscallConn()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
var ip string
|
||||
var port uint16
|
||||
err = rawConn.Control(func(fd uintptr) {
|
||||
addr, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ip = net.IP(addr.Multiaddr[4:8]).String()
|
||||
port = uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3])
|
||||
})
|
||||
return net.JoinHostPort(ip, strconv.Itoa(int(port))), nil
|
||||
}
|
23
core/process/transparent_others.go
Normal file
23
core/process/transparent_others.go
Normal file
@ -0,0 +1,23 @@
|
||||
// +build !linux
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/enet"
|
||||
)
|
||||
|
||||
type TransparentProcess struct {
|
||||
DefaultProcess
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) GetName() string {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) GetZhName() string {
|
||||
return "透明代理"
|
||||
}
|
||||
|
||||
func (tp *TransparentProcess) ProcessConn(c enet.Conn) (bool, error) {
|
||||
return false, nil
|
||||
}
|
138
core/rule/list.go
Normal file
138
core/rule/list.go
Normal file
@ -0,0 +1,138 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/limiter"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/server"
|
||||
"github.com/fatih/structtag"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var orderMap map[string]int
|
||||
var nowOrder = 2<<8 - 1
|
||||
|
||||
type children map[string]*List
|
||||
|
||||
var chains children
|
||||
var limiters children
|
||||
|
||||
func init() {
|
||||
orderMap = make(map[string]int, 0)
|
||||
chains = make(map[string]*List, 0)
|
||||
limiters = make(map[string]*List, 0)
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.HttpHandler{}).Append(&process.HttpServeProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{}, &action.AdminAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.HttpsHandler{}).Append(&process.HttpsServeProcess{HttpServeProcess: process.HttpServeProcess{}}).AppendMany(&action.NpcAction{}, &action.LocalAction{}, &action.AdminAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.HttpsHandler{}).Append(&process.HttpsRedirectProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.HttpHandler{}).Append(&process.HttpProxyProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.HttpsHandler{}).Append(&process.HttpsProxyProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.Socks5Handler{}).Append(&process.Socks5Process{}).AppendMany(&action.LocalAction{}, &action.NpcAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.TransparentHandler{}).Append(&process.TransparentProcess{}).AppendMany(&action.NpcAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.RdpHandler{}).Append(&process.DefaultProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.RedisHandler{}).Append(&process.DefaultProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
|
||||
chains.Append(&server.UdpServer{}).Append(&handler.DnsHandler{}).Append(&process.DefaultProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
// TODO p2p
|
||||
chains.Append(&server.UdpServer{}).Append(&handler.P2PHandler{}).Append(&process.DefaultProcess{}).AppendMany(&action.NpcAction{}, &action.LocalAction{})
|
||||
chains.Append(&server.UdpServer{}).Append(&handler.QUICHandler{}).Append(&process.DefaultProcess{}).AppendMany(&action.BridgeAction{})
|
||||
chains.Append(&server.UdpServer{}).Append(&handler.Socks5UdpHandler{}).Append(&process.Socks5Process{}).AppendMany(&action.LocalAction{}, &action.NpcAction{})
|
||||
|
||||
chains.Append(&server.TcpServer{}).Append(&handler.DefaultHandler{}).Append(&process.DefaultProcess{}).AppendMany(&action.BridgeAction{}, &action.AdminAction{}, &action.NpcAction{}, &action.LocalAction{})
|
||||
|
||||
limiters.AppendMany(&limiter.RateLimiter{}, &limiter.ConnNumLimiter{}, &limiter.FlowLimiter{}, &limiter.IpConnNumLimiter{})
|
||||
}
|
||||
|
||||
func GetLimiters() children {
|
||||
return limiters
|
||||
}
|
||||
|
||||
func GetChains() children {
|
||||
return chains
|
||||
}
|
||||
|
||||
type NameInterface interface {
|
||||
GetName() string
|
||||
GetZhName() string
|
||||
}
|
||||
|
||||
type List struct {
|
||||
ZhName string `json:"zh_name"`
|
||||
Self interface{} `json:"-"`
|
||||
Field []field `json:"field"`
|
||||
Children children `json:"children"`
|
||||
}
|
||||
|
||||
func (c children) AppendMany(child ...NameInterface) {
|
||||
for _, cd := range child {
|
||||
c.Append(cd)
|
||||
}
|
||||
}
|
||||
|
||||
func (c children) Append(child NameInterface) children {
|
||||
if v, ok := c[child.GetName()]; ok {
|
||||
return v.Children
|
||||
}
|
||||
if _, ok := orderMap[child.GetName()]; !ok {
|
||||
orderMap[child.GetName()] = nowOrder
|
||||
nowOrder--
|
||||
}
|
||||
cd := &List{Self: child, Field: getFieldName(child), Children: make(map[string]*List, 0), ZhName: child.GetZhName()}
|
||||
c[child.GetName()] = cd
|
||||
return cd.Children
|
||||
}
|
||||
|
||||
type field struct {
|
||||
FiledType string `json:"field_type"`
|
||||
FieldName string `json:"field_name"`
|
||||
FieldZhName string `json:"field_zh_name"`
|
||||
FieldRequired bool `json:"field_required"`
|
||||
FieldExample string `json:"field_example"`
|
||||
}
|
||||
|
||||
func getFieldName(structName interface{}, child ...bool) []field {
|
||||
result := make([]field, 0)
|
||||
t := reflect.TypeOf(structName)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return result
|
||||
}
|
||||
fieldNum := t.NumField()
|
||||
for i := 0; i < fieldNum; i++ {
|
||||
if len(child) == 0 && t.Field(i).Type.Kind() == reflect.Struct {
|
||||
value := reflect.ValueOf(structName)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
if value.Field(i).CanInterface() {
|
||||
result = append(result, getFieldName(value.Field(i).Interface(), true)...)
|
||||
}
|
||||
}
|
||||
tags, err := structtag.Parse(string(t.Field(i).Tag))
|
||||
if err == nil {
|
||||
tag, err := tags.Get("json")
|
||||
if err == nil {
|
||||
f := field{}
|
||||
f.FiledType = t.Field(i).Type.Kind().String()
|
||||
f.FieldName = tag.Name
|
||||
tag, err = tags.Get("required")
|
||||
if err == nil {
|
||||
f.FieldRequired, _ = strconv.ParseBool(tag.Name)
|
||||
}
|
||||
tag, err = tags.Get("placeholder")
|
||||
if err == nil {
|
||||
f.FieldExample = tag.Name
|
||||
}
|
||||
tag, err = tags.Get("zh_name")
|
||||
if err == nil {
|
||||
f.FieldZhName = tag.Name
|
||||
}
|
||||
result = append(result, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
13
core/rule/list_test.go
Normal file
13
core/rule/list_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/process"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetFields(t *testing.T) {
|
||||
h := process.HttpsServeProcess{HttpServeProcess: process.HttpServeProcess{}}
|
||||
if len(getFieldName(h)) < 3 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
71
core/rule/rule.go
Normal file
71
core/rule/rule.go
Normal file
@ -0,0 +1,71 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/limiter"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/server"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Server server.Server `json:"server"`
|
||||
Handler handler.Handler `json:"handler"`
|
||||
Process process.Process `json:"process"`
|
||||
Action action.Action `json:"action"`
|
||||
Limiters []limiter.Limiter `json:"limiters"`
|
||||
}
|
||||
|
||||
var servers map[string]server.Server
|
||||
|
||||
func init() {
|
||||
servers = make(map[string]server.Server, 0)
|
||||
}
|
||||
|
||||
func (r *Rule) GetHandler() handler.Handler {
|
||||
return r.Handler
|
||||
}
|
||||
|
||||
func (r *Rule) Init() error {
|
||||
s := r.Server
|
||||
var ok bool
|
||||
if s, ok = servers[r.Server.GetName()+":"+r.Server.GetServerAddr()]; !ok {
|
||||
s = r.Server
|
||||
err := s.Init()
|
||||
servers[r.Server.GetName()+":"+r.Server.GetServerAddr()] = s
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go s.Serve()
|
||||
}
|
||||
s.RegisterHandle(r)
|
||||
r.Handler.AddRule(r)
|
||||
if err := r.Action.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range r.Limiters {
|
||||
if err := l.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return r.Process.Init(r.Action)
|
||||
}
|
||||
|
||||
func (r *Rule) RunConn(c enet.Conn) (bool, error) {
|
||||
var err error
|
||||
for _, lm := range r.Limiters {
|
||||
if c, err = lm.DoLimit(c); err != nil {
|
||||
return true, errors.Wrap(err, "rule run")
|
||||
}
|
||||
}
|
||||
if err = c.Reset(0); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return r.Process.ProcessConn(c)
|
||||
}
|
||||
|
||||
func (r *Rule) RunPacketConn(pc enet.PacketConn) (bool, error) {
|
||||
return r.Process.ProcessPacketConn(pc)
|
||||
}
|
92
core/rule/rule_json.go
Normal file
92
core/rule/rule_json.go
Normal file
@ -0,0 +1,92 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/limiter"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/server"
|
||||
"encoding/json"
|
||||
"github.com/pkg/errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type JsonData struct {
|
||||
ObjType string `json:"obj_type"`
|
||||
ObjData string `json:"obj_data"`
|
||||
}
|
||||
|
||||
type JsonRule struct {
|
||||
Name string `json:"name"`
|
||||
Uuid string `json:"uuid"`
|
||||
Status int `json:"status"`
|
||||
Extend int `json:"extend"`
|
||||
Server JsonData `json:"server"`
|
||||
Handler JsonData `json:"handler"`
|
||||
Process JsonData `json:"process"`
|
||||
Action JsonData `json:"action"`
|
||||
Limiters []JsonData `json:"limiters"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
var NotFoundError = errors.New("not found")
|
||||
|
||||
func (jd *JsonRule) ToRule() (*Rule, error) {
|
||||
r := &Rule{Limiters: make([]limiter.Limiter, 0)}
|
||||
s, ok := chains[jd.Server.ObjType]
|
||||
if !ok {
|
||||
return nil, NotFoundError
|
||||
}
|
||||
r.Server = clone(s.Self).(server.Server)
|
||||
err := json.Unmarshal([]byte(jd.Server.ObjData), r.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, ok := s.Children[jd.Handler.ObjType]
|
||||
if !ok {
|
||||
return nil, NotFoundError
|
||||
}
|
||||
r.Handler = clone(h.Self).(handler.Handler)
|
||||
err = json.Unmarshal([]byte(jd.Handler.ObjData), r.Handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, ok := h.Children[jd.Process.ObjType]
|
||||
if !ok {
|
||||
return nil, NotFoundError
|
||||
}
|
||||
r.Process = clone(p.Self).(process.Process)
|
||||
err = json.Unmarshal([]byte(jd.Process.ObjData), r.Process)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, ok := p.Children[jd.Action.ObjType]
|
||||
if !ok {
|
||||
return nil, NotFoundError
|
||||
}
|
||||
r.Action = clone(a.Self).(action.Action)
|
||||
err = json.Unmarshal([]byte(jd.Action.ObjData), r.Action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range jd.Limiters {
|
||||
l, ok := limiters[v.ObjType]
|
||||
if !ok {
|
||||
return nil, NotFoundError
|
||||
}
|
||||
lm := clone(l.Self).(limiter.Limiter)
|
||||
err = json.Unmarshal([]byte(v.ObjData), lm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Limiters = append(r.Limiters, lm)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func clone(i interface{}) interface{} {
|
||||
v := reflect.ValueOf(i).Elem()
|
||||
vNew := reflect.New(v.Type())
|
||||
vNew.Elem().Set(v)
|
||||
return vNew.Interface()
|
||||
}
|
58
core/rule/rule_json_test.go
Normal file
58
core/rule/rule_json_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/server"
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
type person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
a := &person{
|
||||
Name: "ALice",
|
||||
Age: 20,
|
||||
}
|
||||
b := clone(a).(*person)
|
||||
assert.Equal(t, a.Name, b.Name)
|
||||
assert.Equal(t, a.Age, b.Age)
|
||||
a.Name = "Bob"
|
||||
a.Age = 21
|
||||
assert.NotEqual(t, a.Name, b.Name)
|
||||
assert.NotEqual(t, a.Age, b.Age)
|
||||
assert.NotEqual(t, reflect.ValueOf(a).Pointer(), reflect.ValueOf(b).Pointer())
|
||||
}
|
||||
|
||||
func getJson(t *testing.T, i interface{}) string {
|
||||
b, err := json.Marshal(i)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, string(b))
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func TestJsonRule(t *testing.T) {
|
||||
s := &server.TcpServer{ ServerAddr: "127.0.0.1:0"}
|
||||
h := &handler.HttpHandler{}
|
||||
p := &process.HttpServeProcess{}
|
||||
a := &action.LocalAction{}
|
||||
js := JsonRule{
|
||||
Uuid: "",
|
||||
Server: JsonData{s.GetName(), getJson(t, s)},
|
||||
Handler: JsonData{h.GetName(), getJson(t, h)},
|
||||
Process: JsonData{p.GetName(), getJson(t, p)},
|
||||
Action: JsonData{a.GetName(), getJson(t, a)},
|
||||
Limiters: make([]JsonData, 0),
|
||||
}
|
||||
rl, err := js.ToRule()
|
||||
assert.NoError(t, err)
|
||||
err = rl.Init()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rl.Server.(*server.TcpServer).ServerAddr, "127.0.0.1:0")
|
||||
}
|
45
core/rule/rule_test.go
Normal file
45
core/rule/rule_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/action"
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/limiter"
|
||||
"ehang.io/nps/core/process"
|
||||
"ehang.io/nps/core/server"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRule(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NoError(t, err)
|
||||
r := &Rule{
|
||||
Server: &server.TcpServer{ServerAddr: "127.0.0.1:0"},
|
||||
Handler: &handler.DefaultHandler{},
|
||||
Process: &process.DefaultProcess{},
|
||||
Action: &action.LocalAction{TargetAddr: []string{ln.Addr().String()}},
|
||||
Limiters: make([]limiter.Limiter, 0),
|
||||
}
|
||||
err = r.Init()
|
||||
assert.NoError(t, err)
|
||||
data := []byte("test")
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
assert.NoError(t, err)
|
||||
b := make([]byte, 1024)
|
||||
n, err := conn.Read(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, data, b[:n])
|
||||
_, err = conn.Write(b[:n])
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
conn, err := net.Dial(r.Server.GetName(), r.Server.GetServerAddr())
|
||||
assert.NoError(t, err)
|
||||
_, err = conn.Write(data)
|
||||
assert.NoError(t, err)
|
||||
b := make([]byte, 1024)
|
||||
n, err := conn.Read(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, b[:n], data)
|
||||
}
|
30
core/rule/sort.go
Normal file
30
core/rule/sort.go
Normal file
@ -0,0 +1,30 @@
|
||||
package rule
|
||||
|
||||
import "ehang.io/nps/core/process"
|
||||
|
||||
type Sort []*Rule
|
||||
|
||||
func (s Sort) Len() int { return len(s) }
|
||||
func (s Sort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
// Less rule sort by
|
||||
func (s Sort) Less(i, j int) bool {
|
||||
iHandlerSort := orderMap[s[i].Handler.GetName()]
|
||||
iProcessSort := orderMap[s[i].Process.GetName()]
|
||||
jHandlerSort := orderMap[s[j].Handler.GetName()]
|
||||
jProcessSort := orderMap[s[j].Process.GetName()]
|
||||
iSort := iHandlerSort<<16 | iProcessSort<<8
|
||||
jSort := jHandlerSort<<16 | jProcessSort<<8
|
||||
if vi, ok := s[i].Process.(*process.HttpServeProcess); ok {
|
||||
if vj, ok := s[j].Process.(*process.HttpServeProcess); ok {
|
||||
iSort = iSort | (len(vj.RouteUrl) & (2 ^ 8 - 1))
|
||||
jSort = jSort | (len(vi.RouteUrl) & (2 ^ 8 - 1))
|
||||
}
|
||||
}
|
||||
if vi, ok := s[i].Process.(*process.HttpsServeProcess); ok {
|
||||
if vj, ok := s[j].Process.(*process.HttpsServeProcess); ok {
|
||||
iSort = iSort | (len(vj.RouteUrl) & (2 ^ 8 - 1))
|
||||
jSort = jSort | (len(vi.RouteUrl) & (2 ^ 8 - 1))
|
||||
}
|
||||
}
|
||||
return iSort > jSort
|
||||
}
|
27
core/rule/sort_test.go
Normal file
27
core/rule/sort_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/core/process"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSort_Len(t *testing.T) {
|
||||
r1 := &Rule{Handler: &handler.DefaultHandler{}, Process: &process.TransparentProcess{}}
|
||||
r2 := &Rule{Handler: &handler.DefaultHandler{}, Process: &process.DefaultProcess{}}
|
||||
r3 := &Rule{Handler: &handler.DefaultHandler{}, Process: &process.HttpServeProcess{RouteUrl: "/test/aaa"}}
|
||||
r4 := &Rule{Handler: &handler.DefaultHandler{}, Process: &process.Socks5Process{}}
|
||||
r5 := &Rule{Handler: &handler.DefaultHandler{}, Process: &process.HttpServeProcess{RouteUrl: "/test"}}
|
||||
r6 := &Rule{Handler: &handler.HttpsHandler{}, Process: &process.HttpsProxyProcess{}}
|
||||
s := make(Sort, 0)
|
||||
s = append(s, r1, r2, r3, r4, r5, r6)
|
||||
sort.Sort(s)
|
||||
expected := make(Sort, 0)
|
||||
expected = append(expected, r6, r5, r3, r4, r1, r2)
|
||||
for k, v := range expected {
|
||||
if v != s[k] {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
32
core/server/server.go
Normal file
32
core/server/server.go
Normal file
@ -0,0 +1,32 @@
|
||||
package server
|
||||
|
||||
import "ehang.io/nps/core/handler"
|
||||
|
||||
type rule interface {
|
||||
handler.RuleRun
|
||||
GetHandler() handler.Handler
|
||||
}
|
||||
|
||||
type Server interface {
|
||||
Init() error
|
||||
Serve()
|
||||
GetServerAddr() string
|
||||
GetName() string
|
||||
GetZhName() string
|
||||
RegisterHandle(rl rule)
|
||||
}
|
||||
|
||||
type BaseServer struct {
|
||||
handlers map[string]handler.Handler
|
||||
}
|
||||
|
||||
func (bs *BaseServer) RegisterHandle(rl rule) {
|
||||
var h handler.Handler
|
||||
var ok bool
|
||||
if h, ok = bs.handlers[rl.GetHandler().GetName()]; !ok {
|
||||
h = rl.GetHandler()
|
||||
bs.handlers[h.GetName()] = h
|
||||
}
|
||||
h.AddRule(rl)
|
||||
return
|
||||
}
|
97
core/server/tcp.go
Normal file
97
core/server/tcp.go
Normal file
@ -0,0 +1,97 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/lib/enet"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"ehang.io/nps/lib/pool"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
var bp = pool.NewBufferPool(1500)
|
||||
|
||||
type TcpServer struct {
|
||||
BaseServer
|
||||
ServerAddr string `json:"server_addr" required:"true" placeholder:"0.0.0.0:8080 or :8080" zh_name:"监听地址"`
|
||||
listener net.Listener
|
||||
gp *ants.PoolWithFunc
|
||||
}
|
||||
|
||||
func (cm *TcpServer) GetServerAddr() string {
|
||||
if cm.listener == nil {
|
||||
return cm.ServerAddr
|
||||
}
|
||||
return cm.listener.Addr().String()
|
||||
}
|
||||
|
||||
func (cm *TcpServer) Init() error {
|
||||
var err error
|
||||
cm.handlers = make(map[string]handler.Handler, 0)
|
||||
if err = cm.listen(); err != nil {
|
||||
return err
|
||||
}
|
||||
cm.gp, err = ants.NewPoolWithFunc(1000000, func(i interface{}) {
|
||||
rc := enet.NewReaderConn(i.(net.Conn))
|
||||
buf := bp.Get()
|
||||
defer bp.Put(buf)
|
||||
|
||||
if _, err := io.ReadAtLeast(rc, buf, 3); err != nil {
|
||||
logger.Warn("read handle type fom connection failed", zap.String("remote addr", rc.RemoteAddr().String()))
|
||||
_ = rc.Close()
|
||||
return
|
||||
}
|
||||
logger.Debug("read handle type", zap.Uint8("type 1", buf[0]), zap.Uint8("type 2", buf[1]),
|
||||
zap.Uint8("type 3", buf[2]), zap.String("remote addr", rc.RemoteAddr().String()))
|
||||
|
||||
for _, h := range cm.handlers {
|
||||
err = rc.Reset(0)
|
||||
if err != nil {
|
||||
logger.Warn("reset connection error", zap.Error(err), zap.String("remote addr", rc.RemoteAddr().String()))
|
||||
_ = rc.Close()
|
||||
return
|
||||
}
|
||||
ok, err := h.HandleConn(buf, rc)
|
||||
if err != nil {
|
||||
logger.Warn("handle connection error", zap.Error(err), zap.String("remote addr", rc.RemoteAddr().String()))
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
logger.Debug("handle connection success", zap.String("remote addr", rc.RemoteAddr().String()))
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *TcpServer) GetName() string {
|
||||
return "tcp"
|
||||
}
|
||||
|
||||
func (cm *TcpServer) GetZhName() string {
|
||||
return "tcp服务"
|
||||
}
|
||||
|
||||
// create a listener accept user and npc
|
||||
func (cm *TcpServer) listen() error {
|
||||
var err error
|
||||
cm.listener, err = net.Listen("tcp", cm.ServerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *TcpServer) Serve() {
|
||||
for {
|
||||
c, err := cm.listener.Accept()
|
||||
if err != nil {
|
||||
logger.Error("accept enet error", zap.Error(err))
|
||||
break
|
||||
}
|
||||
_ = cm.gp.Invoke(c)
|
||||
}
|
||||
}
|
80
core/server/udp.go
Normal file
80
core/server/udp.go
Normal file
@ -0,0 +1,80 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"ehang.io/nps/core/handler"
|
||||
"ehang.io/nps/lib/logger"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
)
|
||||
|
||||
type UdpServer struct {
|
||||
ServerAddr string `json:"server_addr" required:"true" placeholder:"0.0.0.0:8080 or :8080" zh_name:"监听地址"`
|
||||
gp *ants.PoolWithFunc
|
||||
packetConn net.PacketConn
|
||||
handlers map[string]handler.Handler
|
||||
}
|
||||
|
||||
type udpPacket struct {
|
||||
n int
|
||||
buf []byte
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func (us *UdpServer) Init() error {
|
||||
us.handlers = make(map[string]handler.Handler, 0)
|
||||
if err := us.listen(); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
us.gp, err = ants.NewPoolWithFunc(1000000, func(i interface{}) {
|
||||
p := i.(*udpPacket)
|
||||
defer bp.Put(p.buf)
|
||||
|
||||
logger.Debug("accept a now packet", zap.String("remote addr", p.addr.String()))
|
||||
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (us *UdpServer) GetServerAddr() string {
|
||||
if us.packetConn == nil {
|
||||
return us.ServerAddr
|
||||
}
|
||||
return us.packetConn.LocalAddr().String()
|
||||
}
|
||||
|
||||
func (us *UdpServer) GetName() string {
|
||||
return "udp"
|
||||
}
|
||||
|
||||
func (us *UdpServer) GetZhName() string {
|
||||
return "udp服务"
|
||||
}
|
||||
|
||||
func (us *UdpServer) listen() error {
|
||||
addr, err := net.ResolveUDPAddr("udp", us.ServerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
us.packetConn, err = net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us *UdpServer) Serve() {
|
||||
for {
|
||||
buf := bp.Get()
|
||||
n, addr, err := us.packetConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
logger.Error("accept packet failed", zap.Error(err))
|
||||
break
|
||||
}
|
||||
err = us.gp.Invoke(udpPacket{n: n, buf: buf, addr: addr})
|
||||
if err != nil {
|
||||
logger.Error("Invoke error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
216
db/db.go
Normal file
216
db/db.go
Normal file
@ -0,0 +1,216 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"ehang.io/nps/lib/logger"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
glog "gorm.io/gorm/logger"
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Db interface {
|
||||
Init() error
|
||||
SetConfig(key string, val string) error
|
||||
GetConfig(key string) (string, error)
|
||||
Insert(table string, uuid string, data string) error
|
||||
Delete(table string, uuid string) error
|
||||
Update(table string, uuid string, data string) error
|
||||
Count(table string, filterValue string) (int64, error)
|
||||
QueryOne(table string, uuid string) (string, error)
|
||||
QueryAll(table string, filterValue string) ([]string, error)
|
||||
QueryPage(table string, limit int, offset int, filterValue string) ([]string, error)
|
||||
}
|
||||
|
||||
type dbLogger struct{}
|
||||
|
||||
func (_ dbLogger) Write(p []byte) (n int, err error) {
|
||||
logger.Warn(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var _ Db = (*SqliteDb)(nil)
|
||||
|
||||
type SqliteDb struct {
|
||||
Path string
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSqliteDb(path string) *SqliteDb {
|
||||
return &SqliteDb{Path: path}
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) SetConfig(key string, val string) error {
|
||||
c := &Config{Key: key}
|
||||
err := sd.db.First(c, "key = ?", key).Error
|
||||
if err != nil {
|
||||
return sd.db.Create(&Config{Key: key, Val: val}).Error
|
||||
}
|
||||
return sd.db.Model(c).Update("val", val).Error
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) GetConfig(key string) (string, error) {
|
||||
c := &Config{}
|
||||
err := sd.db.First(c, "key = ?", key).Error
|
||||
return c.Val, err
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) Count(table string, filterValue string) (int64, error) {
|
||||
i, err := sd.GetTable(table, "", "")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var count int64
|
||||
err = sd.db.Where("data LIKE ?", "%"+filterValue+"%").Model(i).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) QueryOne(table string, uuid string) (string, error) {
|
||||
i, err := sd.GetTable(table, "", "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = sd.db.First(i, "uuid = ?", uuid).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getData(i), err
|
||||
}
|
||||
|
||||
func getData(i interface{}) string {
|
||||
immutable := reflect.ValueOf(i)
|
||||
return immutable.Elem().FieldByName("Data").String()
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) QueryAll(table string, filterValue string) (data []string, err error) {
|
||||
list := make([]string, 0)
|
||||
switch table {
|
||||
case "rule":
|
||||
var r []Rule
|
||||
d := sd.db.Order("id desc")
|
||||
if filterValue != "" {
|
||||
d = d.Where("data LIKE ?", "%"+filterValue+"%")
|
||||
}
|
||||
err = d.Find(&r).Error
|
||||
for _, v := range r {
|
||||
list = append(list, v.Data)
|
||||
}
|
||||
case "cert":
|
||||
var c []Cert
|
||||
d := sd.db.Order("id desc")
|
||||
if filterValue != "" {
|
||||
d = d.Where("data LIKE ?", "%"+filterValue+"%")
|
||||
}
|
||||
err = d.Find(&c).Error
|
||||
for _, v := range c {
|
||||
list = append(list, v.Data)
|
||||
}
|
||||
default:
|
||||
err = errors.New("error table")
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) QueryPage(table string, limit int, offset int, filterValue string) (data []string, err error) {
|
||||
list := make([]string, 0)
|
||||
switch table {
|
||||
case "rule":
|
||||
var r []Rule
|
||||
d := sd.db.Limit(limit).Offset(offset)
|
||||
if filterValue != "" {
|
||||
d = d.Where("data LIKE ?", "%"+filterValue+"%")
|
||||
}
|
||||
err = d.Order("id desc").Find(&r).Error
|
||||
for _, v := range r {
|
||||
list = append(list, v.Data)
|
||||
}
|
||||
case "cert":
|
||||
var c []Cert
|
||||
d := sd.db.Limit(limit).Offset(offset)
|
||||
if filterValue != "" {
|
||||
d = d.Where("data LIKE ?", "%"+filterValue+"%")
|
||||
}
|
||||
err = d.Order("id desc").Find(&c).Error
|
||||
for _, v := range c {
|
||||
list = append(list, v.Data)
|
||||
}
|
||||
default:
|
||||
err = errors.New("error table")
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) Insert(table string, uuid string, data string) error {
|
||||
i, err := sd.GetTable(table, uuid, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sd.db.Create(i).Error
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) Delete(table string, uuid string) error {
|
||||
i, err := sd.GetTable(table, uuid, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sd.db.Where("uuid", uuid).Delete(i).Error
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) Update(table string, uuid string, data string) error {
|
||||
i, err := sd.GetTable(table, uuid, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sd.db.Model(i).Where("uuid", uuid).Update("data", data).Error
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) Init() error {
|
||||
var err error
|
||||
newLogger := glog.New(
|
||||
log.New(dbLogger{}, "\r\n", log.LstdFlags),
|
||||
glog.Config{
|
||||
SlowThreshold: time.Second,
|
||||
LogLevel: glog.Silent,
|
||||
IgnoreRecordNotFoundError: true,
|
||||
Colorful: false,
|
||||
},
|
||||
)
|
||||
sd.db, err = gorm.Open(sqlite.Open(sd.Path), &gorm.Config{Logger: newLogger})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sd.db.AutoMigrate(&Cert{}, &Rule{}, &Config{})
|
||||
}
|
||||
|
||||
func (sd *SqliteDb) GetTable(table string, uuid string, data string) (i interface{}, err error) {
|
||||
switch table {
|
||||
case "rule":
|
||||
i = &Rule{Uuid: uuid, Data: data}
|
||||
case "cert":
|
||||
i = &Cert{Uuid: uuid, Data: data}
|
||||
default:
|
||||
err = errors.New("error table")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Cert struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Uuid string `gorm:"column:uuid" json:"uuid"`
|
||||
Data string `gorm:"column:data" json:"data"`
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Uuid string `gorm:"column:uuid" json:"uuid"`
|
||||
Data string `gorm:"column:data" json:"data"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Key string `gorm:"column:key" json:"key"`
|
||||
Val string `gorm:"column:val" json:"val"`
|
||||
}
|
73
db/db_test.go
Normal file
73
db/db_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSqlite(t *testing.T) {
|
||||
err := os.Remove(filepath.Join(os.TempDir(), "test.db"))
|
||||
assert.NoError(t, err)
|
||||
d := NewSqliteDb(filepath.Join(os.TempDir(), "test.db"))
|
||||
err = d.Init()
|
||||
assert.NoError(t, err)
|
||||
for _, tableName := range []string{"rule", "cert"} {
|
||||
var firstUuid, lastUuid string
|
||||
for i := 0; i < 1000; i++ {
|
||||
uid := uuid.NewV4().String()
|
||||
if i == 0 {
|
||||
firstUuid = uid
|
||||
}
|
||||
lastUuid = uid
|
||||
err = d.Insert(tableName, uid, "test"+strconv.Itoa(i))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
n, err := d.Count(tableName,"")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(n), 1000)
|
||||
list, err := d.QueryAll(tableName, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(list), 1000)
|
||||
one, err := d.QueryOne(tableName, firstUuid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, one, "test0")
|
||||
list, err = d.QueryPage(tableName, 10, 10, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(list), 10)
|
||||
assert.Equal(t, list[0], "test989")
|
||||
err = d.Delete(tableName, lastUuid)
|
||||
assert.NoError(t, err)
|
||||
n, err = d.Count(tableName,"")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, n, int64(999))
|
||||
one, err = d.QueryOne(tableName, firstUuid)
|
||||
assert.NoError(t, err)
|
||||
err = d.Update(tableName, firstUuid, "test_new")
|
||||
assert.NoError(t, err)
|
||||
one, err = d.QueryOne(tableName, firstUuid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, one, "test_new")
|
||||
}
|
||||
err = d.SetConfig("test_key1", "test_val1")
|
||||
assert.NoError(t, err)
|
||||
v, err := d.GetConfig("test_key1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, v, "test_val1")
|
||||
v, err = d.GetConfig("test_key2")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, v, "")
|
||||
err = d.SetConfig("test_key2", "test_val2")
|
||||
assert.NoError(t, err)
|
||||
v, err = d.GetConfig("test_key2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, v, "test_val2")
|
||||
err = d.SetConfig("test_key1", "test_val2")
|
||||
assert.NoError(t, err)
|
||||
v, err = d.GetConfig("test_key1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, v, "test_val2")
|
||||
}
|
43
go.mod
Normal file
43
go.mod
Normal file
@ -0,0 +1,43 @@
|
||||
module ehang.io/nps
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/appleboy/gin-jwt v2.5.0+incompatible // indirect
|
||||
github.com/appleboy/gin-jwt/v2 v2.7.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/gin-contrib/cache v1.1.0 // indirect
|
||||
github.com/gin-contrib/cors v1.3.1 // indirect
|
||||
github.com/gin-gonic/gin v1.7.6 // indirect
|
||||
github.com/gliderlabs/ssh v0.3.3 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87
|
||||
github.com/icodeface/grdp v0.0.0-20200414055757-e0008b0b5cb2 // indirect
|
||||
github.com/knadh/koanf v1.3.3 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.24.0
|
||||
github.com/marten-seemann/qtls v0.10.0 // indirect
|
||||
github.com/miekg/dns v1.1.43 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.4.6 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/tidwall/gjson v1.12.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||
go.uber.org/zap v1.19.1
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/dgrijalva/jwt-go.v3 v3.2.0 // indirect
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gorm.io/driver/sqlite v1.2.6 // indirect
|
||||
gorm.io/gorm v1.22.3 // indirect
|
||||
)
|
546
go.sum
Normal file
546
go.sum
Normal file
@ -0,0 +1,546 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/appleboy/gin-jwt v2.5.0+incompatible h1:oLQTP1fiGDoDKoC2UDqXD9iqCP44ABIZMMenfH/xCqw=
|
||||
github.com/appleboy/gin-jwt v2.5.0+incompatible/go.mod h1:pG7tv32IEe5wEh1NSQzcyD02ZZAqZWp07RdGiIhgaRQ=
|
||||
github.com/appleboy/gin-jwt/v2 v2.7.0 h1:MjbX0OVC1hmb+cYNSW7yrlG8KfIN/X0qn5kqhAsHinY=
|
||||
github.com/appleboy/gin-jwt/v2 v2.7.0/go.mod h1:AP2pmslwuqdHcjua9m8APdgqX9Ksx0ZM34d5RGvUYBU=
|
||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/cache v1.1.0 h1:lM8B4YtzdQQM6ThTlvtNPeBNfW1mNdh/CMFQfenH1dk=
|
||||
github.com/gin-contrib/cache v1.1.0/go.mod h1:9ylpYjLq309/y5hTpyuDxfPG+V6QlSB56vrWe6OhoLQ=
|
||||
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
|
||||
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.7.6 h1:Ma2JlolDP9KCHuHTrW58EIIxVUQKxSxzuCKguCYyFas=
|
||||
github.com/gin-gonic/gin v1.7.6/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA=
|
||||
github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
|
||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I=
|
||||
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/icodeface/grdp v0.0.0-20200414055757-e0008b0b5cb2 h1:ygCqbylErDVlQ3ykPaa8lmfrbPQoiGcOlSgC+Ej8VgI=
|
||||
github.com/icodeface/grdp v0.0.0-20200414055757-e0008b0b5cb2/go.mod h1:AENknrjjTG+yAL3EFNMDxSALP140yz4RXJOqIwGulig=
|
||||
github.com/icodeface/tls v0.0.0-20190904082144-a3e1fe30543e h1:3V+yaobzgt0CfQTbMoTEwDY5qbvrVnRgr96JBZ00Vhw=
|
||||
github.com/icodeface/tls v0.0.0-20190904082144-a3e1fe30543e/go.mod h1:VJNHW2GxCtQP/IQtXykBIPBV8maPJ/dHWirVTwm9GwY=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/knadh/koanf v1.3.3 h1:eNtBOzQDzkzIIPRCJCx/Ha3DeD/ZFwCAp8JxyqoVAls=
|
||||
github.com/knadh/koanf v1.3.3/go.mod h1:1cfH5223ZeZUOs8FU2UdTmaNfHpqgtjV0+NHjRO43gs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lucas-clemente/quic-go v0.24.0 h1:ToR7SIIEdrgOhgVTHvPgdVRJfgVy+N0wQAagH7L4d5g=
|
||||
github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
|
||||
github.com/lunixbochs/struc v0.0.0-20190326164542-a9e4041416c2 h1:xvBq0/ARZLqmB57m6jds017I+KtXPcsKBHv6dUUac4A=
|
||||
github.com/lunixbochs/struc v0.0.0-20190326164542-a9e4041416c2/go.mod h1:iOJu9pApjjmEmNq7PqlA5R9mDu/HMF5EM3llWKX/TyA=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/memcachier/mc v2.0.1+incompatible h1:s8EDz0xrJLP8goitwZOoq1vA/sm0fPS4X3KAF0nyhWQ=
|
||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/panjf2000/ants/v2 v2.4.6 h1:drmj9mcygn2gawZ155dRbo+NfXEfAssjZNU1qoIb4gQ=
|
||||
github.com/panjf2000/ants/v2 v2.4.6/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tidwall/gjson v1.9.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
|
||||
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
|
||||
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
|
||||
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c h1:WtYZ93XtWSO5KlOMgPZu7hXY9WhMZpprvlm5VwvAl8c=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/dgrijalva/jwt-go.v3 v3.2.0 h1:N46iQqOtHry7Hxzb9PGrP68oovQmj7EhudNoKHvbOvI=
|
||||
gopkg.in/dgrijalva/jwt-go.v3 v3.2.0/go.mod h1:hdNXC2Z9yC029rvsQ/on2ZNQ44Z2XToVhpXXbR+J05A=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
|
||||
gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
|
||||
gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA=
|
||||
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
34
lib/cert/cert.go
Normal file
34
lib/cert/cert.go
Normal file
@ -0,0 +1,34 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetCertSnFromConfig return SerialNumber by tls.Config
|
||||
func GetCertSnFromConfig(config *tls.Config) (string, error) {
|
||||
if len(config.Certificates) == 0 || len(config.Certificates[0].Certificate) == 0 {
|
||||
return "", errors.New("certificates is empty")
|
||||
}
|
||||
return GetCertSnFromBlock(config.Certificates[0].Certificate[0])
|
||||
}
|
||||
|
||||
// GetCertSnFromEncode return SerialNumber by encoded cert
|
||||
func GetCertSnFromEncode(b []byte) (string, error) {
|
||||
block, _ := pem.Decode(b)
|
||||
if block == nil {
|
||||
return "", errors.New("block is not a cert encoded")
|
||||
}
|
||||
return GetCertSnFromBlock(block.Bytes)
|
||||
}
|
||||
|
||||
// GetCertSnFromBlock return SerialNumber by decode block
|
||||
func GetCertSnFromBlock(block []byte) (string, error) {
|
||||
cert, err := x509.ParseCertificate(block)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "ParseCertificate")
|
||||
}
|
||||
return cert.SerialNumber.String(), nil
|
||||
}
|
42
lib/cert/cert_test.go
Normal file
42
lib/cert/cert_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetCertSerialNumber(t *testing.T) {
|
||||
g := NewX509Generator(pkix.Name{
|
||||
Country: []string{"CN"},
|
||||
Organization: []string{"Ehang.io"},
|
||||
OrganizationalUnit: []string{"nps"},
|
||||
Province: []string{"Beijing"},
|
||||
CommonName: "nps",
|
||||
Locality: []string{"Beijing"},
|
||||
})
|
||||
cert, key, err := g.CreateRootCa()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "cert.pem"), cert, 0600))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(os.TempDir(), "key.pem"), key, 0600))
|
||||
assert.NoError(t, err)
|
||||
|
||||
cliCrt, err := tls.LoadX509KeyPair(filepath.Join(os.TempDir(), "cert.pem"), filepath.Join(os.TempDir(), "key.pem"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cliCrt},
|
||||
}
|
||||
sn1, err := GetCertSnFromConfig(config)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, sn1)
|
||||
|
||||
sn2, err := GetCertSnFromEncode(cert)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, sn2)
|
||||
|
||||
assert.Equal(t, sn1, sn2)
|
||||
}
|
253
lib/cert/client_hello.go
Normal file
253
lib/cert/client_hello.go
Normal file
@ -0,0 +1,253 @@
|
||||
package cert
|
||||
|
||||
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
|
||||
}
|
108
lib/cert/generate.go
Normal file
108
lib/cert/generate.go
Normal file
@ -0,0 +1,108 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ Generator = (*X509Generator)(nil)
|
||||
|
||||
type Generator interface {
|
||||
CreateRootCa() ([]byte, []byte, error)
|
||||
CreateCert(dnsName string) ([]byte, []byte, error)
|
||||
InitRootCa(rootCa []byte, rootKey []byte) error
|
||||
}
|
||||
|
||||
type X509Generator struct {
|
||||
rootCert *x509.Certificate
|
||||
rootRsaPrivate *rsa.PrivateKey
|
||||
subject pkix.Name
|
||||
}
|
||||
|
||||
func NewX509Generator(subject pkix.Name) *X509Generator {
|
||||
return &X509Generator{
|
||||
subject: subject,
|
||||
}
|
||||
}
|
||||
|
||||
func (cg *X509Generator) InitRootCa(rootCa []byte, rootKey []byte) error {
|
||||
var err error
|
||||
caBlock, _ := pem.Decode(rootCa)
|
||||
cg.rootCert, err = x509.ParseCertificate(caBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyBlock, _ := pem.Decode(rootKey)
|
||||
cg.rootRsaPrivate, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cg *X509Generator) CreateCert(dnsName string) ([]byte, []byte, error) {
|
||||
return cg.create(false, dnsName)
|
||||
}
|
||||
|
||||
func (cg *X509Generator) CreateRootCa() ([]byte, []byte, error) {
|
||||
return cg.create(true, "")
|
||||
}
|
||||
|
||||
func (cg *X509Generator) create(isRootCa bool, dnsName string) ([]byte, []byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: cg.subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(3, 0, 0),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
|
||||
DNSNames: []string{dnsName},
|
||||
}
|
||||
|
||||
if isRootCa {
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
}
|
||||
|
||||
priKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var ca []byte
|
||||
if !isRootCa {
|
||||
if cg.rootCert == nil || cg.rootRsaPrivate == nil {
|
||||
return nil, nil, errors.New("root ca is not exist")
|
||||
}
|
||||
ca, err = x509.CreateCertificate(rand.Reader, template, cg.rootCert, &priKey.PublicKey, cg.rootRsaPrivate)
|
||||
} else {
|
||||
ca, err = x509.CreateCertificate(rand.Reader, template, template, &priKey.PublicKey, priKey)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caPem := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: ca,
|
||||
}
|
||||
ca = pem.EncodeToMemory(caPem)
|
||||
|
||||
buf := x509.MarshalPKCS1PrivateKey(priKey)
|
||||
keyPem := &pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: buf,
|
||||
}
|
||||
key := pem.EncodeToMemory(keyPem)
|
||||
return ca, key, nil
|
||||
}
|
62
lib/cert/generate_test.go
Normal file
62
lib/cert/generate_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateCert(t *testing.T) {
|
||||
dnsName := "ehang.io"
|
||||
g := NewX509Generator(pkix.Name{
|
||||
Country: []string{"CN"},
|
||||
Organization: []string{"ehang.io"},
|
||||
OrganizationalUnit: []string{"nps"},
|
||||
Province: []string{"Beijing"},
|
||||
CommonName: "nps",
|
||||
Locality: []string{"Beijing"},
|
||||
})
|
||||
// generate root ca
|
||||
rootCa, rootKey, err := g.CreateRootCa()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = g.InitRootCa(rootCa, rootKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// generate npc cert
|
||||
clientCa, _, err := g.CreateCert(dnsName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify npc cert by root cert
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM(rootCa)
|
||||
if !ok {
|
||||
panic("failed to parse root certificate")
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(clientCa)
|
||||
if block == nil {
|
||||
t.Fatal("failed to parse certificate PEM")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatal("failed to parse certificate: " + err.Error())
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
DNSName: dnsName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
if _, err := cert.Verify(opts); err != nil {
|
||||
t.Fatal("failed to verify certificate: " + err.Error())
|
||||
}
|
||||
|
||||
}
|
141
lib/common/addr.go
Normal file
141
lib/common/addr.go
Normal file
@ -0,0 +1,141 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var AddrError = errors.New("addr error")
|
||||
|
||||
// SOCKS address types as defined in RFC 1928 section 5.
|
||||
const (
|
||||
AtypIPv4 = 1
|
||||
AtypDomainName = 3
|
||||
AtypIPv6 = 4
|
||||
)
|
||||
|
||||
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
||||
const MaxAddrLen = 1 + 1 + 255 + 2
|
||||
|
||||
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
|
||||
type Addr []byte
|
||||
|
||||
// String serializes SOCKS address a to string form.
|
||||
func (a Addr) String() string {
|
||||
var host, port string
|
||||
|
||||
switch a[0] { // address type
|
||||
case AtypDomainName:
|
||||
host = string(a[2 : 2+int(a[1])])
|
||||
port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1]))
|
||||
case AtypIPv4:
|
||||
host = net.IP(a[1 : 1+net.IPv4len]).String()
|
||||
port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
|
||||
case AtypIPv6:
|
||||
host = net.IP(a[1 : 1+net.IPv6len]).String()
|
||||
port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
|
||||
}
|
||||
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
func readAddr(r io.Reader, b []byte) (Addr, error) {
|
||||
if len(b) < MaxAddrLen {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
case AtypDomainName:
|
||||
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
|
||||
return b[:1+1+int(b[1])+2], err
|
||||
case AtypIPv4:
|
||||
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
|
||||
return b[:1+net.IPv4len+2], err
|
||||
case AtypIPv6:
|
||||
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
|
||||
return b[:1+net.IPv6len+2], err
|
||||
}
|
||||
|
||||
return nil, errors.New("addr type not supported")
|
||||
}
|
||||
|
||||
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
||||
func ReadAddr(r io.Reader) (Addr, error) {
|
||||
return readAddr(r, make([]byte, MaxAddrLen))
|
||||
}
|
||||
|
||||
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
||||
func SplitAddr(b []byte) (Addr, error) {
|
||||
addrLen := 1
|
||||
if len(b) < addrLen {
|
||||
return nil, AddrError
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
case AtypDomainName:
|
||||
if len(b) < 2 {
|
||||
return nil, AddrError
|
||||
}
|
||||
addrLen = 1 + 1 + int(b[1]) + 2
|
||||
case AtypIPv4:
|
||||
addrLen = 1 + net.IPv4len + 2
|
||||
case AtypIPv6:
|
||||
addrLen = 1 + net.IPv6len + 2
|
||||
default:
|
||||
return nil, AddrError
|
||||
|
||||
}
|
||||
|
||||
if len(b) < addrLen {
|
||||
return nil, AddrError
|
||||
}
|
||||
|
||||
return b[:addrLen], nil
|
||||
}
|
||||
|
||||
// ParseAddr parses the address in string s. Returns nil if failed.
|
||||
func ParseAddr(s string) (Addr, error) {
|
||||
var addr Addr
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return nil, AddrError
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
addr = make([]byte, 1+net.IPv4len+2)
|
||||
addr[0] = AtypIPv4
|
||||
copy(addr[1:], ip4)
|
||||
} else {
|
||||
addr = make([]byte, 1+net.IPv6len+2)
|
||||
addr[0] = AtypIPv6
|
||||
copy(addr[1:], ip)
|
||||
}
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return nil, AddrError
|
||||
}
|
||||
addr = make([]byte, 1+1+len(host)+2)
|
||||
addr[0] = AtypDomainName
|
||||
addr[1] = byte(len(host))
|
||||
copy(addr[2:], host)
|
||||
}
|
||||
|
||||
portnum, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil, AddrError
|
||||
}
|
||||
|
||||
addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum)
|
||||
|
||||
return addr, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user