Files

83 lines
2.1 KiB
Go

// Package tcpproxy forwards opaque TCP streams without terminating TLS or
// interpreting application authentication. Access control belongs to Manager.
package tcpproxy
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"strconv"
"time"
"linkport/internal/model"
)
type Handler struct{}
func New() *Handler { return &Handler{} }
func dialTarget(ctx context.Context, p model.Proxy, dial model.DialFunc) (net.Conn, error) {
dc, cancel := context.WithTimeout(ctx, p.ConnectionOptions.ConnectTimeout(10*time.Second))
defer cancel()
c, err := dial(dc, "tcp", net.JoinHostPort(p.TargetHost, strconv.Itoa(p.TargetPort)))
if err != nil {
if p.Outbound.Mode != "" && p.Outbound.Mode != "direct" {
return nil, err
}
return nil, model.AtConnectionStage("target_connect", err)
}
return c, nil
}
func (*Handler) Test(ctx context.Context, p model.Proxy, dial model.DialFunc) (model.TestResult, error) {
c, err := dialTarget(ctx, p, dial)
if err != nil {
return model.TestResult{}, err
}
c.Close()
// A successful dial does not validate TLS, credentials or application health.
return model.TestResult{Role: "tcp"}, nil
}
func (*Handler) Serve(ctx context.Context, client net.Conn, p model.Proxy, dial model.DialFunc, _ *tls.Config, _ model.EventFunc) error {
defer client.Close()
upstream, err := dialTarget(ctx, p, dial)
if err != nil {
return err
}
defer upstream.Close()
closeBoth := func() { client.Close(); upstream.Close() }
stop := context.AfterFunc(ctx, closeBoth)
defer stop()
results := make(chan error, 2)
copyStream := func(dst, src net.Conn) {
_, err := io.Copy(dst, src)
if err == nil {
if half, ok := dst.(interface{ CloseWrite() error }); ok {
err = half.CloseWrite()
} else {
// Some outbound stream implementations do not support half-close.
closeBoth()
}
}
if err != nil {
closeBoth()
}
results <- err
}
go copyStream(upstream, client)
go copyStream(client, upstream)
first, second := <-results, <-results
if ctx.Err() != nil {
return ctx.Err()
}
for _, err := range []error{first, second} {
if err != nil && !errors.Is(err, net.ErrClosed) {
return err
}
}
return nil
}