Files
coder/coderd/devtunnel/tunnel.go

94 lines
2.3 KiB
Go

package devtunnel
import (
"context"
"fmt"
"net/url"
"os"
"strconv"
"strings"
frpclient "github.com/fatedier/frp/client"
frpconfig "github.com/fatedier/frp/pkg/config"
frpconsts "github.com/fatedier/frp/pkg/consts"
frplog "github.com/fatedier/frp/pkg/util/log"
frpcrypto "github.com/fatedier/golib/crypto"
"github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator"
"golang.org/x/xerrors"
)
// New creates a new tunnel pointing at the URL provided. Once created, it
// returns the external hostname that will resolve to it.
//
// The tunnel will exit when the context provided is canceled.
//
// Upstream connection occurs synchronously through a selfhosted
// https://github.com/fatedier/frp instance. The error channel sends an error
// when the frp client stops.
func New(ctx context.Context, coderurl *url.URL) (string, <-chan error, error) {
frpcrypto.DefaultSalt = "frp"
cfg := frpconfig.GetDefaultClientConf()
cfg.ServerAddr = "frp-tunnel.coder.app"
cfg.ServerPort = 7000
// Ignore all logs from frp.
frplog.InitLog("file", os.DevNull, "error", -1, false)
var (
id = uuid.NewString()
subdomain = strings.ReplaceAll(namesgenerator.GetRandomName(1), "_", "-")
portStr = coderurl.Port()
)
if portStr == "" {
portStr = "80"
}
port, err := strconv.ParseInt(portStr, 10, 64)
if err != nil {
return "", nil, xerrors.Errorf("parse port %q: %w", port, err)
}
httpcfg := map[string]frpconfig.ProxyConf{
id: &frpconfig.HTTPProxyConf{
BaseProxyConf: frpconfig.BaseProxyConf{
ProxyName: id,
ProxyType: frpconsts.HTTPProxy,
UseEncryption: false,
UseCompression: false,
LocalSvrConf: frpconfig.LocalSvrConf{
LocalIP: coderurl.Hostname(),
LocalPort: int(port),
},
},
DomainConf: frpconfig.DomainConf{
SubDomain: subdomain,
},
Locations: []string{""},
},
}
if err := httpcfg[id].CheckForCli(); err != nil {
return "", nil, xerrors.Errorf("check for cli: %w", err)
}
svc, err := frpclient.NewService(cfg, httpcfg, nil, "")
if err != nil {
return "", nil, xerrors.Errorf("create new proxy service: %w", err)
}
ch := make(chan error, 1)
go func() {
err := svc.Run()
ch <- err
close(ch)
}()
go func() {
<-ctx.Done()
svc.Close()
}()
return fmt.Sprintf("https://%s.try.coder.app", subdomain), ch, nil
}