实现多路复用

This commit is contained in:
lwch
2021-08-23 15:47:55 +08:00
parent 6e1d30f79f
commit 4fbaab1326
11 changed files with 468 additions and 369 deletions
-187
View File
@@ -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()
}
-108
View File
@@ -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)
}
-64
View File
@@ -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
}
+6
View File
@@ -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,
+10 -10
View File
@@ -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)
}
+77
View File
@@ -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)
}
+141
View File
@@ -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)
}
+70
View File
@@ -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
}
+51
View File
@@ -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
}
+112
View File
@@ -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
}
+1
View File
@@ -1,5 +1,6 @@
id: this # 客户端ID
server: 127.0.0.1:6154 # 服务器地址
links: 3 # 与server的连接数
secret: 0123456789 # 预共享密钥,必须与server端相同,否则握手失败
log:
dir: ./logs # 路径