mirror of
https://github.com/ehang-io/nps.git
synced 2025-09-08 00:26:52 +00:00
add new file
This commit is contained in:
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
|
||||
}
|
Reference in New Issue
Block a user