go-tunnel/client.go

215 lines
4.6 KiB
Go
Raw Normal View History

2020-10-21 18:00:15 +02:00
package tunnel
import (
"context"
2020-10-26 19:42:07 +01:00
"encoding/json"
2020-10-21 18:00:15 +02:00
"io"
"net"
2020-10-24 13:35:27 +02:00
"time"
2020-10-21 18:00:15 +02:00
"gitlab.com/wpetit/goweb/logger"
"github.com/pkg/errors"
"github.com/xtaci/kcp-go/v5"
"github.com/xtaci/smux"
)
type Client struct {
2020-10-26 19:42:07 +01:00
conf *ClientConfig
conn *kcp.UDPSession
sess *smux.Session
2020-10-21 18:00:15 +02:00
}
func (c *Client) Connect(ctx context.Context) error {
2020-10-26 19:42:07 +01:00
logger.Debug(ctx, "connecting", logger.F("serverAddr", c.conf.ServerAddress))
2020-10-21 18:00:15 +02:00
conn, err := kcp.DialWithOptions(
c.conf.ServerAddress, c.conf.BlockCrypt,
c.conf.DataShards, c.conf.ParityShards,
)
if err != nil {
return errors.WithStack(err)
}
2020-10-23 18:26:50 +02:00
if c.conf.ConfigureConn != nil {
if err := c.conf.ConfigureConn(conn); err != nil {
return errors.WithStack(err)
}
}
2020-10-21 18:00:15 +02:00
2020-10-26 19:42:07 +01:00
sess, err := smux.Client(conn, c.conf.SmuxConfig)
2020-10-21 18:00:15 +02:00
if err != nil {
return errors.WithStack(err)
}
2020-10-26 19:42:07 +01:00
stream, err := sess.OpenStream()
if err != nil {
2020-10-21 18:00:15 +02:00
return errors.WithStack(err)
}
2020-10-26 19:42:07 +01:00
defer stream.Close()
2020-10-21 18:00:15 +02:00
2020-10-26 19:42:07 +01:00
success, err := c.authenticate(ctx, stream)
2020-10-21 18:00:15 +02:00
if err != nil {
return errors.WithStack(err)
}
if !success {
2020-10-26 19:42:07 +01:00
return errors.WithStack(ErrAuthenticationFailed)
2020-10-21 18:00:15 +02:00
}
2020-10-26 19:42:07 +01:00
logger.Debug(ctx, "authentication success")
2020-10-24 13:35:27 +02:00
c.conn = conn
c.sess = sess
2020-10-21 18:00:15 +02:00
return nil
}
func (c *Client) Listen(ctx context.Context) error {
2020-10-26 19:42:07 +01:00
logger.Debug(ctx, "listening for proxy requests")
2020-10-21 18:00:15 +02:00
2020-10-26 19:42:07 +01:00
for {
stream, err := c.sess.AcceptStream()
if err != nil {
return errors.WithStack(err)
}
2020-10-24 13:35:27 +02:00
2020-10-26 19:42:07 +01:00
subCtx := logger.With(ctx,
logger.F("remoteAddr", stream.RemoteAddr()),
logger.F("localAddr", stream.LocalAddr()),
)
2020-10-21 18:00:15 +02:00
2020-10-26 19:42:07 +01:00
readDeadline := time.Now().Add(c.conf.ProxyRequestTimeout)
logger.Debug(subCtx, "waiting for proxy request", logger.F("deadline", readDeadline))
2020-10-21 18:00:15 +02:00
2020-10-26 19:42:07 +01:00
if err := stream.SetReadDeadline(readDeadline); err != nil {
stream.Close()
logger.Error(subCtx, "could not set read deadline", logger.E(errors.WithStack(err)))
continue
}
2020-10-21 18:00:15 +02:00
2020-10-26 19:42:07 +01:00
decoder := json.NewDecoder(stream)
proxyReq := &proxyRequest{}
if err := decoder.Decode(proxyReq); err != nil {
stream.Close()
logger.Error(subCtx, "could not decode proxy request", logger.E(errors.WithStack(err)))
continue
}
if err := stream.SetReadDeadline(time.Time{}); err != nil {
stream.Close()
logger.Error(subCtx, "could not set read deadline", logger.E(errors.WithStack(err)))
continue
}
go c.handleProxyStream(subCtx, stream, proxyReq.Network, proxyReq.Address)
}
2020-10-21 18:00:15 +02:00
}
func (c *Client) Close() error {
2020-10-26 19:42:07 +01:00
if c.sess != nil && !c.sess.IsClosed() {
if err := c.sess.Close(); err != nil {
return errors.WithStack(err)
}
2020-10-21 18:00:15 +02:00
}
2020-10-26 19:42:07 +01:00
if c.conn != nil {
if err := c.conn.Close(); err != nil {
return errors.WithStack(err)
}
2020-10-21 18:00:15 +02:00
}
2020-10-26 19:42:07 +01:00
c.conn = nil
c.sess = nil
2020-10-21 18:00:15 +02:00
return nil
}
2020-10-26 19:42:07 +01:00
func (c *Client) authenticate(ctx context.Context, stream *smux.Stream) (bool, error) {
encoder := json.NewEncoder(stream)
authReq := &authRequest{
Credentials: c.conf.Credentials,
2020-10-21 18:00:15 +02:00
}
2020-10-26 19:42:07 +01:00
start := time.Now()
writeDeadline := start.Add(c.conf.AuthenticationTimeout)
logger.Debug(ctx, "sending auth request", logger.F("deadline", writeDeadline))
2020-10-23 17:08:42 +02:00
2020-10-26 19:42:07 +01:00
if err := stream.SetWriteDeadline(writeDeadline); err != nil {
return false, errors.WithStack(err)
}
2020-10-24 13:35:27 +02:00
2020-10-26 19:42:07 +01:00
if err := encoder.Encode(authReq); err != nil {
return false, errors.WithStack(err)
}
decoder := json.NewDecoder(stream)
authRes := &authResponse{}
readDeadline := time.Now().Add(c.conf.AuthenticationTimeout - time.Now().Sub(start))
logger.Debug(ctx, "waiting for auth response", logger.F("deadline", readDeadline))
if err := stream.SetReadDeadline(readDeadline); err != nil {
return false, errors.WithStack(err)
2020-10-21 18:00:15 +02:00
}
2020-10-26 19:42:07 +01:00
if err := decoder.Decode(authRes); err != nil && !errors.Is(err, io.EOF) {
return false, errors.WithStack(err)
}
2020-10-23 17:08:42 +02:00
2020-10-26 19:42:07 +01:00
return authRes.Success, nil
2020-10-24 13:35:27 +02:00
}
2020-10-23 17:08:42 +02:00
2020-10-26 19:42:07 +01:00
func (c *Client) handleProxyStream(ctx context.Context, in *smux.Stream, network, address string) {
defer func(start time.Time) {
logger.Debug(ctx, "handleProxyStream duration", logger.F("duration", time.Since(start)))
}(time.Now())
defer in.Close()
2020-10-23 17:08:42 +02:00
2020-10-26 19:42:07 +01:00
logger.Debug(
ctx, "proxying",
logger.F("network", network),
logger.F("address", address),
)
out, err := net.Dial(network, address)
2020-10-24 13:35:27 +02:00
if err != nil {
2020-10-26 19:42:07 +01:00
logger.Error(ctx, "could not dial", logger.E(errors.WithStack(err)))
2020-10-23 17:08:42 +02:00
2020-10-24 13:35:27 +02:00
return
}
2020-10-26 19:42:07 +01:00
defer out.Close()
2020-10-23 17:08:42 +02:00
2020-10-24 13:35:27 +02:00
streamCopy := func(dst io.Writer, src io.ReadCloser) {
if _, err := Copy(dst, src); err != nil {
if errors.Is(err, smux.ErrInvalidProtocol) {
2020-10-26 19:42:07 +01:00
logger.Error(ctx, "could not proxy", logger.E(errors.WithStack(err)))
2020-10-23 17:08:42 +02:00
}
}
2020-10-24 13:35:27 +02:00
in.Close()
out.Close()
}
2020-10-26 19:42:07 +01:00
go streamCopy(out, in)
streamCopy(in, out)
2020-10-21 18:00:15 +02:00
}
func NewClient(funcs ...ClientConfigFunc) *Client {
conf := DefaultClientConfig()
for _, fn := range funcs {
fn(conf)
}
return &Client{
2020-10-24 13:35:27 +02:00
conf: conf,
2020-10-21 18:00:15 +02:00
}
}