Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
172 lines (141 sloc) 3.3 KB
package tun2
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"within.website/ln"
"within.website/ln/opname"
kcp "github.com/xtaci/kcp-go"
"github.com/xtaci/smux"
)
// Client connects to a remote tun2 server and sets up authentication before routing
// individual HTTP requests to discrete streams that are reverse proxied to the eventual
// backend.
type Client struct {
cfg *ClientConfig
}
// ClientConfig configures client with settings that the user provides.
type ClientConfig struct {
TLSConfig *tls.Config
ConnType string
ServerAddr string
Token string
Domain string
BackendURL string
// internal use only
forceTCPClear bool
}
// NewClient constructs an instance of Client with a given ClientConfig.
func NewClient(cfg *ClientConfig) (*Client, error) {
if cfg == nil {
return nil, errors.New("tun2: client config needed")
}
c := &Client{
cfg: cfg,
}
return c, nil
}
// Connect dials the remote server and negotiates a client session with its
// configured server address. This will then continuously proxy incoming HTTP
// requests to the backend HTTP server.
//
// This is a blocking function.
func (c *Client) Connect(ctx context.Context) error {
ctx = opname.With(ctx, "tun2.Client.connect")
return c.connect(ctx, c.cfg.ServerAddr)
}
func closeLater(ctx context.Context, clo io.Closer) {
<-ctx.Done()
clo.Close()
}
func (c *Client) connect(ctx context.Context, serverAddr string) error {
target, err := url.Parse(c.cfg.BackendURL)
if err != nil {
return err
}
s := &http.Server{
Handler: httputil.NewSingleHostReverseProxy(target),
}
go closeLater(ctx, s)
f := ln.F{
"server_addr": serverAddr,
"conn_type": c.cfg.ConnType,
}
var conn net.Conn
switch c.cfg.ConnType {
case "tcp":
if c.cfg.forceTCPClear {
ln.Log(ctx, f, ln.Info("connecting over plain TCP"))
conn, err = net.Dial("tcp", serverAddr)
} else {
conn, err = tls.Dial("tcp", serverAddr, c.cfg.TLSConfig)
}
if err != nil {
return err
}
case "kcp":
kc, err := kcp.Dial(serverAddr)
if err != nil {
return err
}
defer kc.Close()
serverHost, _, _ := net.SplitHostPort(serverAddr)
tc := c.cfg.TLSConfig.Clone()
tc.ServerName = serverHost
conn = tls.Client(kc, tc)
}
go closeLater(ctx, conn)
ln.Log(ctx, f, ln.Info("connected"))
session, err := smux.Client(conn, smux.DefaultConfig())
if err != nil {
return err
}
go closeLater(ctx, session)
controlStream, err := session.AcceptStream()
if err != nil {
return err
}
go closeLater(ctx, controlStream)
authData, err := json.Marshal(&Auth{
Token: c.cfg.Token,
Domain: c.cfg.Domain,
})
if err != nil {
return err
}
_, err = controlStream.Write(authData)
if err != nil {
return err
}
err = s.Serve(&smuxListener{
conn: conn,
session: session,
})
if err != nil {
return err
}
if err := ctx.Err(); err != nil {
ln.Error(ctx, err, f, ln.Info("context error"))
}
return nil
}
// smuxListener wraps a smux session as a net.Listener.
type smuxListener struct {
conn net.Conn
session *smux.Session
}
func (sl *smuxListener) Accept() (net.Conn, error) {
return sl.session.AcceptStream()
}
func (sl *smuxListener) Addr() net.Addr {
return sl.conn.LocalAddr()
}
func (sl *smuxListener) Close() error {
return sl.session.Close()
}
You can’t perform that action at this time.