From 4fbaab13268ca7d622fe22cfd3ff7f2b49184035 Mon Sep 17 00:00:00 2001 From: lwch Date: Mon, 23 Aug 2021 15:47:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=A4=9A=E8=B7=AF=E5=A4=8D?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/client/client/client.go | 187 ----------------------------------- code/client/client/send.go | 108 -------------------- code/client/client/tunnel.go | 64 ------------ code/client/global/conf.go | 6 ++ code/client/main.go | 20 ++-- code/client/pool/handler.go | 77 +++++++++++++++ code/client/pool/pool.go | 141 ++++++++++++++++++++++++++ code/client/tunnel/link.go | 70 +++++++++++++ code/client/tunnel/send.go | 51 ++++++++++ code/client/tunnel/tunnel.go | 112 +++++++++++++++++++++ conf/client.yaml | 1 + 11 files changed, 468 insertions(+), 369 deletions(-) delete mode 100644 code/client/client/client.go delete mode 100644 code/client/client/send.go delete mode 100644 code/client/client/tunnel.go create mode 100644 code/client/pool/handler.go create mode 100644 code/client/pool/pool.go create mode 100644 code/client/tunnel/link.go create mode 100644 code/client/tunnel/send.go create mode 100644 code/client/tunnel/tunnel.go diff --git a/code/client/client/client.go b/code/client/client/client.go deleted file mode 100644 index 9c1cb31..0000000 --- a/code/client/client/client.go +++ /dev/null @@ -1,187 +0,0 @@ -package client - -import ( - "context" - "fmt" - "natpass/code/client/global" - "natpass/code/network" - "net" - "strings" - "sync" - "time" - - "github.com/lwch/logging" - "github.com/lwch/runtime" -) - -// Client client -type Client struct { - sync.RWMutex - cfg *global.Configure - conn *network.Conn - links map[string]*link -} - -// New create client -func New(cfg *global.Configure, conn *network.Conn) *Client { - return &Client{ - cfg: cfg, - conn: conn, - links: make(map[string]*link), - } -} - -// Run main loop -func (c *Client) Run() { - err := c.writeHandshake() - runtime.Assert(err) - logging.Info("%s connected", c.cfg.Server) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - go c.keepalive(ctx) - - for _, t := range c.cfg.Tunnels { - if t.Type == "tcp" { - go c.handleTcpTunnel(ctx, t) - } else { - go c.handleUdpTunnel(ctx, t) - } - } - - for { - msg, err := c.conn.ReadMessage(time.Second) - if err != nil { - if strings.Contains(err.Error(), "i/o timeout") { - continue - } - logging.Error("read message: %v", err) - return - } - switch msg.GetXType() { - case network.Msg_connect_req: - c.handleConnect(ctx, msg.GetFrom(), msg.GetTo(), msg.GetCreq()) - case network.Msg_disconnect: - c.handleDisconnect(msg.GetXDisconnect()) - case network.Msg_forward: - c.handleData(msg.GetXData()) - } - } -} - -// handleTcpTunnel local listen to tcp tunnel -func (c *Client) handleTcpTunnel(ctx context.Context, t global.Tunnel) { - defer func() { - if err := recover(); err != nil { - logging.Error("close tcp tunnel: %s, err=%v", t.Name, err) - } - }() - l, err := net.ListenTCP("tcp", &net.TCPAddr{ - IP: net.ParseIP(t.LocalAddr), - Port: int(t.LocalPort), - }) - runtime.Assert(err) - defer l.Close() - for { - select { - case <-ctx.Done(): - return - default: - } - conn, err := l.Accept() - if err != nil { - logging.Error("accept from %s tunnel, err=%v", t.Name, err) - continue - } - - id, err := runtime.UUID(16, "0123456789abcdef") - if err != nil { - conn.Close() - logging.Error("generate link id failed, err=%v", err) - continue - } - - link := newLink(id, t.Name, t.Target, c, conn) - c.sendConnect(link.id, t) - - c.Lock() - c.links[link.id] = link - c.Unlock() - - go link.loop(ctx) - } -} - -func (c *Client) keepalive(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - default: - } - time.Sleep(10 * time.Second) - c.sendKeepalive() - } -} - -// handleUdpTunnel local listen to udp tunnel -func (c *Client) handleUdpTunnel(ctx context.Context, t global.Tunnel) { - // TODO -} - -// handleConnect handle connect request message from remote, local dial to remomte addr -func (c *Client) handleConnect(ctx context.Context, from, to string, req *network.ConnectRequest) { - dial := "tcp" - if req.GetXType() == network.ConnectRequest_udp { - dial = "udp" - } - conn, err := net.Dial(dial, fmt.Sprintf("%s:%d", req.GetAddr(), req.GetPort())) - if err != nil { - c.connectError(to, req.GetId(), err.Error()) - return - } - link := newLink(req.GetId(), req.GetName(), from, c, conn) - c.Lock() - c.links[link.id] = link - c.Unlock() - c.connectOK(to, req.GetId()) - go link.loop(ctx) -} - -// handleDisconnect handle disconnect message from remote, this means remote connection is closed -func (c *Client) handleDisconnect(data *network.Disconnect) { - id := data.GetId() - - c.RLock() - tn := c.links[id] - c.RUnlock() - - if tn != nil { - tn.close() - - c.Lock() - delete(c.links, id) - c.Unlock() - } -} - -// handleData handle forward data message, write data to local connection -func (c *Client) handleData(data *network.Data) { - id := data.GetLid() - c.RLock() - tn := c.links[id] - c.RUnlock() - if tn == nil { - logging.Error("link %s not found", id) - return - } - tn.write(data.GetData()) -} - -func (c *Client) closeLink(l *link) { - l.close() - c.Lock() - delete(c.links, l.id) - c.Unlock() -} diff --git a/code/client/client/send.go b/code/client/client/send.go deleted file mode 100644 index 07e335a..0000000 --- a/code/client/client/send.go +++ /dev/null @@ -1,108 +0,0 @@ -package client - -import ( - "natpass/code/client/global" - "natpass/code/network" - "time" -) - -func (c *Client) writeHandshake() error { - var msg network.Msg - msg.XType = network.Msg_handshake - msg.From = c.cfg.ID - msg.To = "server" - msg.Payload = &network.Msg_Hsp{ - Hsp: &network.HandshakePayload{ - Enc: c.cfg.Enc[:], - }, - } - return c.conn.WriteMessage(&msg, 5*time.Second) -} - -func (c *Client) sendConnect(id string, t global.Tunnel) { - tp := network.ConnectRequest_tcp - if t.Type != "tcp" { - tp = network.ConnectRequest_udp - } - var msg network.Msg - msg.From = c.cfg.ID - msg.To = t.Target - msg.XType = network.Msg_connect_req - msg.Payload = &network.Msg_Creq{ - Creq: &network.ConnectRequest{ - Id: id, - Name: t.Name, - XType: tp, - Addr: t.RemoteAddr, - Port: uint32(t.RemotePort), - }, - } - c.conn.WriteMessage(&msg, 5*time.Second) -} - -func (c *Client) connectError(to, id, m string) { - var msg network.Msg - msg.From = c.cfg.ID - msg.To = to - msg.XType = network.Msg_connect_rep - msg.Payload = &network.Msg_Crep{ - Crep: &network.ConnectResponse{ - Id: id, - Ok: false, - Msg: m, - }, - } - c.conn.WriteMessage(&msg, time.Second) -} - -func (c *Client) connectOK(to, id string) { - var msg network.Msg - msg.From = c.cfg.ID - msg.To = to - msg.XType = network.Msg_connect_rep - msg.Payload = &network.Msg_Crep{ - Crep: &network.ConnectResponse{ - Id: id, - Ok: true, - }, - } - c.conn.WriteMessage(&msg, time.Second) -} - -func (c *Client) send(id, target string, data []byte) { - var msg network.Msg - msg.From = c.cfg.ID - msg.To = target - msg.XType = network.Msg_forward - msg.Payload = &network.Msg_XData{ - XData: &network.Data{ - Lid: id, - Data: data, - }, - } - c.conn.WriteMessage(&msg, time.Second) -} - -func (c *Client) disconnect(id, to string) { - var msg network.Msg - msg.From = c.cfg.ID - msg.To = to - msg.XType = network.Msg_disconnect - msg.Payload = &network.Msg_XDisconnect{ - XDisconnect: &network.Disconnect{ - Id: id, - }, - } - c.conn.WriteMessage(&msg, time.Second) - c.Lock() - delete(c.links, id) - c.Unlock() -} - -func (c *Client) sendKeepalive() { - var msg network.Msg - msg.From = c.cfg.ID - msg.To = "server" - msg.XType = network.Msg_keepalive - c.conn.WriteMessage(&msg, time.Second) -} diff --git a/code/client/client/tunnel.go b/code/client/client/tunnel.go deleted file mode 100644 index 501baed..0000000 --- a/code/client/client/tunnel.go +++ /dev/null @@ -1,64 +0,0 @@ -package client - -import ( - "bytes" - "context" - "io" - "net" - - "github.com/lwch/logging" -) - -type link struct { - cli *Client - id string // link id - name string // tunnel name - target string // remote client id - c net.Conn -} - -func newLink(id, name, target string, cli *Client, conn net.Conn) *link { - logging.Info("create link %s: %s", name, id) - return &link{ - cli: cli, - id: id, - name: name, - target: target, - c: conn, - } -} - -func (l *link) close() { - logging.Info("disconnect tunnel %s on link %s", l.name, l.id) - err := l.c.Close() - if err == nil { - l.cli.disconnect(l.id, l.target) - } -} - -func (l *link) loop(ctx context.Context) { - defer l.cli.closeLink(l) - buf := make([]byte, 32*1024) - for { - select { - case <-ctx.Done(): - return - default: - } - n, err := l.c.Read(buf) - if err != nil { - return - } - if n == 0 { - continue - } - logging.Debug("link %s on tunnel %s read from local %d bytes", l.id, l.name, n) - l.cli.send(l.id, l.target, buf[:n]) - } -} - -func (l *link) write(data []byte) error { - logging.Debug("link %s on tunnel %s write from remote %d bytes", l.id, l.name, len(data)) - _, err := io.Copy(l.c, bytes.NewReader(data)) - return err -} diff --git a/code/client/global/conf.go b/code/client/global/conf.go index 05f32fe..aec1256 100644 --- a/code/client/global/conf.go +++ b/code/client/global/conf.go @@ -25,6 +25,7 @@ type Configure struct { ID string Server string Enc [md5.Size]byte + Links int LogDir string LogSize utils.Bytes LogRotate int @@ -37,6 +38,7 @@ func LoadConf(dir string) *Configure { ID string `yaml:"id"` Server string `yaml:"server"` Secret string `yaml:"secret"` + Links int `yaml:"links"` Log struct { Dir string `yaml:"dir"` Size utils.Bytes `yaml:"size"` @@ -54,10 +56,14 @@ func LoadConf(dir string) *Configure { } cfg.Tunnel[i] = t } + if cfg.Links <= 0 { + cfg.Links = 3 + } return &Configure{ ID: cfg.ID, Server: cfg.Server, Enc: md5.Sum([]byte(cfg.Secret)), + Links: cfg.Links, LogDir: cfg.Log.Dir, LogSize: cfg.Log.Size, LogRotate: cfg.Log.Rotate, diff --git a/code/client/main.go b/code/client/main.go index 730a4ce..381b22c 100644 --- a/code/client/main.go +++ b/code/client/main.go @@ -1,17 +1,15 @@ package main import ( - "crypto/tls" "flag" "fmt" - "natpass/code/client/client" "natpass/code/client/global" - "natpass/code/network" + "natpass/code/client/pool" + "natpass/code/client/tunnel" "os" "github.com/lwch/daemon" "github.com/lwch/logging" - "github.com/lwch/runtime" ) var ( @@ -57,11 +55,13 @@ func main() { logging.SetSizeRotate(cfg.LogDir, "np-cli", int(cfg.LogSize.Bytes()), cfg.LogRotate, true) defer logging.Flush() - conn, err := tls.Dial("tcp", cfg.Server, nil) - runtime.Assert(err) - c := network.NewConn(conn) - defer c.Close() + pl := pool.New(cfg.Links) - cli := client.New(cfg, c) - cli.Run() + for _, t := range cfg.Tunnels { + tn := tunnel.New(t, pl) + pl.Add(tn) + go tn.Handle() + } + + pl.Loop(cfg) } diff --git a/code/client/pool/handler.go b/code/client/pool/handler.go new file mode 100644 index 0000000..ff8b366 --- /dev/null +++ b/code/client/pool/handler.go @@ -0,0 +1,77 @@ +package pool + +import ( + "fmt" + "natpass/code/client/global" + "natpass/code/client/tunnel" + "natpass/code/network" + "net" + "strconv" + "time" + + "github.com/lwch/logging" +) + +func (p *Pool) handleConnect(conn *network.Conn, from, to string, req *network.ConnectRequest) { + dial := "tcp" + if req.GetXType() == network.ConnectRequest_udp { + dial = "udp" + } + link, err := net.Dial(dial, fmt.Sprintf("%s:%d", req.GetAddr(), req.GetPort())) + if err != nil { + p.sendConnectError(conn, to, from, req.GetId(), err.Error()) + return + } + host, pt, _ := net.SplitHostPort(link.LocalAddr().String()) + port, _ := strconv.ParseUint(pt, 10, 16) + tn := tunnel.New(global.Tunnel{ + Name: req.GetName(), + Target: to, + Type: dial, + LocalAddr: host, + LocalPort: uint16(port), + RemoteAddr: req.GetAddr(), + RemotePort: uint16(req.GetPort()), + }, p) + tn.NewLink(req.GetId(), req.GetName(), link, p.writeChannel) + p.Add(tn) +} + +func (p *Pool) handleDisconnect(data *network.Disconnect) { + id := data.GetId() + + p.RLock() + link := p.links[id] + p.RUnlock() + + if link != nil { + link.Close() + } +} + +func (p *Pool) handleData(data *network.Data) { + id := data.GetLid() + p.RLock() + link := p.links[id] + p.RUnlock() + if link == nil { + logging.Error("link %s not found", id) + return + } + link.WriteData(data.GetData()) +} + +func (p *Pool) sendConnectError(conn *network.Conn, from, to, id, m string) { + var msg network.Msg + msg.From = from + msg.To = to + msg.XType = network.Msg_connect_rep + msg.Payload = &network.Msg_Crep{ + Crep: &network.ConnectResponse{ + Id: id, + Ok: false, + Msg: m, + }, + } + conn.WriteMessage(&msg, time.Second) +} diff --git a/code/client/pool/pool.go b/code/client/pool/pool.go new file mode 100644 index 0000000..18ccfdb --- /dev/null +++ b/code/client/pool/pool.go @@ -0,0 +1,141 @@ +package pool + +import ( + "context" + "crypto/tls" + "natpass/code/client/global" + "natpass/code/client/tunnel" + "natpass/code/network" + "strings" + "sync" + "time" + + "github.com/lwch/logging" + "github.com/lwch/runtime" +) + +// Pool connection pool +type Pool struct { + sync.RWMutex + count int + writeChannel chan *network.Msg + tunnels map[string]*tunnel.Tunnel // tunnel name => tunnel + links map[string]*tunnel.Link // link id => link +} + +// New create connection pool +func New(count int) *Pool { + return &Pool{ + count: count, + writeChannel: make(chan *network.Msg, 100), + tunnels: make(map[string]*tunnel.Tunnel), + links: make(map[string]*tunnel.Link), + } +} + +// WriteChan get write channel +func (p *Pool) WriteChan() chan *network.Msg { + return p.writeChannel +} + +// Loop main loop +func (p *Pool) Loop(cfg *global.Configure) { + for i := 0; i < p.count; i++ { + go func() { + for { + p.connect(cfg) + } + }() + } + select {} +} + +// LinkClose on close link +func (p *Pool) LinkClose(name, id string) { + p.Lock() + defer p.Unlock() + if tunnel, ok := p.tunnels[name]; ok { + if len(tunnel.GetLinks()) == 0 { + delete(p.tunnels, name) + } + } + delete(p.links, id) +} + +// connect connect server +func (p *Pool) connect(cfg *global.Configure) { + defer func() { + if err := recover(); err != nil { + logging.Error("connect error: %v", err) + } + }() + conn, err := tls.Dial("tcp", cfg.Server, nil) + runtime.Assert(err) + c := network.NewConn(conn) + defer c.Close() + err = p.writeHandshake(c, cfg) + runtime.Assert(err) + logging.Info("%s connected", cfg.Server) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + for { + select { + case msg := <-p.writeChannel: + msg.From = cfg.ID + c.WriteMessage(msg, time.Second) + case <-ctx.Done(): + return + } + } + }() + for { + msg, err := c.ReadMessage(time.Second) + if err != nil { + if strings.Contains(err.Error(), "i/o timeout") { + continue + } + logging.Error("read message: %v", err) + return + } + switch msg.GetXType() { + case network.Msg_connect_req: + p.handleConnect(c, msg.GetFrom(), msg.GetTo(), msg.GetCreq()) + case network.Msg_connect_rep: + logging.Info("connected") + case network.Msg_disconnect: + p.handleDisconnect(msg.GetXDisconnect()) + case network.Msg_forward: + p.handleData(msg.GetXData()) + } + } +} + +// Add add tunnel +func (p *Pool) Add(tunnel *tunnel.Tunnel) { + p.Lock() + defer p.Unlock() + p.tunnels[tunnel.Name] = tunnel + for _, link := range tunnel.GetLinks() { + p.links[link.ID] = link + } +} + +// AddLink add link +func (p *Pool) AddLink(link *tunnel.Link) { + p.links[link.ID] = link +} + +func (p *Pool) writeHandshake(conn *network.Conn, cfg *global.Configure) error { + var msg network.Msg + msg.XType = network.Msg_handshake + msg.From = cfg.ID + msg.To = "server" + msg.Payload = &network.Msg_Hsp{ + Hsp: &network.HandshakePayload{ + Enc: cfg.Enc[:], + }, + } + return conn.WriteMessage(&msg, 5*time.Second) +} diff --git a/code/client/tunnel/link.go b/code/client/tunnel/link.go new file mode 100644 index 0000000..39571a1 --- /dev/null +++ b/code/client/tunnel/link.go @@ -0,0 +1,70 @@ +package tunnel + +import ( + "bytes" + "io" + "natpass/code/network" + "net" + + "github.com/lwch/logging" +) + +type Link struct { + tunnel *Tunnel + ID string // link id + target string // remote client id + conn net.Conn + write chan *network.Msg + onWork bool + closed bool +} + +func newLink(id, target string, tunnel *Tunnel, conn net.Conn, write chan *network.Msg) *Link { + logging.Info("create link %s: %s", tunnel.Name, id) + return &Link{ + tunnel: tunnel, + ID: id, + target: target, + conn: conn, + write: write, + onWork: false, + closed: false, + } +} + +// Close close link +func (link *Link) Close() { + if link.closed { + return + } + logging.Info("disconnect tunnel %s on link %s", link.tunnel.Name, link.ID) + link.closed = true + err := link.conn.Close() + if err == nil { + link.sendDisconnect(link.ID, link.target) + } + link.tunnel.Close(link) +} + +func (link *Link) loop() { + defer link.Close() + buf := make([]byte, 32*1024) + for { + n, err := link.conn.Read(buf) + if err != nil { + return + } + if n == 0 { + continue + } + logging.Debug("link %s on tunnel %s read from local %d bytes", link.ID, link.tunnel.Name, n) + link.sendData(link.ID, link.target, buf[:n]) + } +} + +// WriteData write data from remote +func (link *Link) WriteData(data []byte) error { + logging.Debug("link %s on tunnel %s write from remote %d bytes", link.ID, link.tunnel.Name, len(data)) + _, err := io.Copy(link.conn, bytes.NewReader(data)) + return err +} diff --git a/code/client/tunnel/send.go b/code/client/tunnel/send.go new file mode 100644 index 0000000..02ec708 --- /dev/null +++ b/code/client/tunnel/send.go @@ -0,0 +1,51 @@ +package tunnel + +import ( + "natpass/code/client/global" + "natpass/code/network" +) + +func (link *Link) sendConnect(id string, t global.Tunnel) { + tp := network.ConnectRequest_tcp + if t.Type != "tcp" { + tp = network.ConnectRequest_udp + } + var msg network.Msg + msg.To = t.Target + msg.XType = network.Msg_connect_req + msg.Payload = &network.Msg_Creq{ + Creq: &network.ConnectRequest{ + Id: id, + Name: t.Name, + XType: tp, + Addr: t.RemoteAddr, + Port: uint32(t.RemotePort), + }, + } + link.write <- &msg +} + +func (link *Link) sendDisconnect(id, to string) { + var msg network.Msg + msg.To = to + msg.XType = network.Msg_disconnect + msg.Payload = &network.Msg_XDisconnect{ + XDisconnect: &network.Disconnect{ + Id: id, + }, + } + link.write <- &msg +} + +func (link *Link) sendData(id, target string, data []byte) { + var msg network.Msg + msg.To = target + msg.XType = network.Msg_forward + msg.Payload = &network.Msg_XData{ + XData: &network.Data{ + Lid: id, + Data: data, + }, + } + link.write <- &msg +} diff --git a/code/client/tunnel/tunnel.go b/code/client/tunnel/tunnel.go new file mode 100644 index 0000000..9a0fcdd --- /dev/null +++ b/code/client/tunnel/tunnel.go @@ -0,0 +1,112 @@ +package tunnel + +import ( + "natpass/code/client/global" + "natpass/code/network" + "net" + "sync" + + "github.com/lwch/logging" + "github.com/lwch/runtime" +) + +type Super interface { + LinkClose(string, string) // tunnel name, link id + WriteChan() chan *network.Msg + AddLink(*Link) +} + +// Tunnel tunnel +type Tunnel struct { + sync.RWMutex + super Super + Name string + cfg global.Tunnel + links map[string]*Link +} + +// New create tunnel +func New(cfg global.Tunnel, super Super) *Tunnel { + return &Tunnel{ + super: super, + Name: cfg.Name, + cfg: cfg, + links: make(map[string]*Link), + } +} + +func (tunnel *Tunnel) NewLink(id, target string, conn net.Conn, write chan *network.Msg) { + link := newLink(id, target, tunnel, conn, write) + tunnel.Lock() + tunnel.links[link.ID] = link + tunnel.Unlock() +} + +// Handle tunnel handler +func (tunnel *Tunnel) Handle() { + if tunnel.cfg.Type == "tcp" { + tunnel.handleTcp() + } else { + // TODO + func() {}() + } +} + +// handleTcp local listen to tcp tunnel +func (tunnel *Tunnel) handleTcp() { + defer func() { + if err := recover(); err != nil { + logging.Error("close tcp tunnel: %s, err=%v", tunnel.cfg.Name, err) + } + }() + l, err := net.ListenTCP("tcp", &net.TCPAddr{ + IP: net.ParseIP(tunnel.cfg.LocalAddr), + Port: int(tunnel.cfg.LocalPort), + }) + runtime.Assert(err) + defer l.Close() + for { + conn, err := l.Accept() + if err != nil { + logging.Error("accept from %s tunnel, err=%v", tunnel.cfg.Name, err) + continue + } + + id, err := runtime.UUID(16, "0123456789abcdef") + if err != nil { + conn.Close() + logging.Error("generate link id failed, err=%v", err) + continue + } + + link := newLink(id, tunnel.cfg.Target, tunnel, conn, tunnel.super.WriteChan()) + link.sendConnect(link.ID, tunnel.cfg) + + tunnel.super.AddLink(link) + tunnel.Lock() + tunnel.links[link.ID] = link + tunnel.Unlock() + + go link.loop() + } +} + +// Close close link +func (tunnel *Tunnel) Close(link *Link) { + link.Close() + tunnel.Lock() + delete(tunnel.links, link.ID) + tunnel.Unlock() + tunnel.super.LinkClose(tunnel.Name, link.ID) +} + +// GetLinks get tunnel links +func (tunnel *Tunnel) GetLinks() []*Link { + ret := make([]*Link, 0, len(tunnel.links)) + tunnel.RLock() + for _, l := range tunnel.links { + ret = append(ret, l) + } + tunnel.RUnlock() + return ret +} diff --git a/conf/client.yaml b/conf/client.yaml index 131d309..e221d3c 100644 --- a/conf/client.yaml +++ b/conf/client.yaml @@ -1,5 +1,6 @@ id: this # 客户端ID server: 127.0.0.1:6154 # 服务器地址 +links: 3 # 与server的连接数 secret: 0123456789 # 预共享密钥,必须与server端相同,否则握手失败 log: dir: ./logs # 路径