Skip to content

Instantly share code, notes, and snippets.

@a5r0n
Last active February 23, 2025 19:28
Show Gist options
  • Save a5r0n/68447f3a5610f1f985357a499147735a to your computer and use it in GitHub Desktop.
Save a5r0n/68447f3a5610f1f985357a499147735a to your computer and use it in GitHub Desktop.
package main
import (
"context"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"runtime"
"strings"
"time"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
"github.com/cloudflare/cloudflared/features"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/orchestration"
"github.com/cloudflare/cloudflared/signal"
"github.com/cloudflare/cloudflared/supervisor"
"github.com/cloudflare/cloudflared/tlsconfig"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
var graceShutdownC chan struct{}
var (
Version = "DEV"
BuildTime = "unknown"
BuildType = ""
bInfo *cliutil.BuildInfo
)
const httpTimeout = 15 * time.Second
func createQuickTunnel(clientID uuid.UUID) (*connection.TunnelProperties, error) {
client := http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: httpTimeout,
ResponseHeaderTimeout: httpTimeout,
},
Timeout: httpTimeout,
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/tunnel", "https://api.trycloudflare.com"), nil)
if err != nil {
return nil, errors.Wrap(err, "failed to build quick tunnel request")
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", bInfo.UserAgent())
resp, err := client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "failed to request quick Tunnel")
}
defer resp.Body.Close()
// This will read the entire response into memory so we can print it in case of error
rsp_body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read quick-tunnel response")
}
var data QuickTunnelResponse
if err := json.Unmarshal(rsp_body, &data); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal quick Tunnel")
}
tunnelID, err := uuid.Parse(data.Result.ID)
if err != nil {
return nil, errors.Wrap(err, "failed to parse quick Tunnel ID")
}
credentials := connection.Credentials{
AccountTag: data.Result.AccountTag,
TunnelSecret: data.Result.Secret,
TunnelID: tunnelID,
}
url := data.Result.Hostname
if !strings.HasPrefix(url, "https://") {
url = "https://" + url
}
fmt.Println(url)
pogslient := pogs.ClientInfo{
ClientID: clientID[:],
Features: []string{},
Version: bInfo.Version(),
Arch: bInfo.OSArch(),
}
return &connection.TunnelProperties{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname, Client: pogslient}, nil
}
func main() {
os.Setenv("QUIC_GO_DISABLE_ECN", "1")
metrics.RegisterBuildInfo(BuildType, BuildTime, Version)
bInfo = cliutil.GetBuildInfo(BuildType, Version)
ctx := context.Background()
clientID, err := uuid.NewRandom()
if err != nil {
panic(errors.Wrap(err, "can't generate connector UUID"))
}
println("Generated Connector ID: " + clientID.String())
logTransport := logger.Create(logger.CreateConfig(
"",
false,
"",
"",
))
observer := connection.NewObserver(logTransport, logTransport) // log, logTransport
ing, err := ingress.ParseIngress(&config.Configuration{
Ingress: []config.UnvalidatedIngressRule{
{
Service: "http://localhost:8080",
},
},
})
if err != nil {
panic(err)
}
orchestrator, err := orchestration.NewOrchestrator(
ctx,
&orchestration.Config{
Ingress: &ing,
WarpRouting: ingress.NewWarpRoutingConfig(&config.WarpRoutingConfig{}), // Defaults
ConfigurationFlags: map[string]string{},
WriteTimeout: 0, // write-stream-timeout, default is 0
},
[]pogs.Tag{},
[]ingress.Rule{},
logTransport,
)
if err != nil {
panic(err)
}
connectedSignal := signal.New(make(chan struct{}))
reconnectCh := make(chan supervisor.ReconnectSignal, 4) // 4 is default
protocolSelector, err := connection.NewProtocolSelector(
connection.HTTP2.String(),
"random value", // TODO credentials account tag. But it's just used as a hash
false,
false,
edgediscovery.ProtocolPercentage,
connection.ResolveTTL,
logTransport,
)
if err != nil {
panic(errors.Wrap(err, "unable to create protocol selector"))
}
edgeTLSConfigs := make(map[connection.Protocol]*tls.Config, len(connection.ProtocolList))
for _, p := range connection.ProtocolList {
tlsSettings := p.TLSSettings()
if tlsSettings == nil {
panic(fmt.Errorf("%s has unknown TLS settings", p))
}
edgeTLSConfig, err := tlsconfig.CreateTunnelConfig(cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil), tlsSettings.ServerName)
if err != nil {
panic(errors.Wrap(err, "unable to create TLS config to connect with edge"))
}
if len(tlsSettings.NextProtos) > 0 {
edgeTLSConfig.NextProtos = tlsSettings.NextProtos
}
edgeTLSConfigs[p] = edgeTLSConfig
}
tunnel, err := createQuickTunnel(clientID)
if err != nil {
panic(err)
}
tunnelConfig := &supervisor.TunnelConfig{
GracePeriod: 30, // grace-period, default is 30
ReplaceExisting: false, // force
OSArch: runtime.GOOS + "_" + runtime.GOARCH,
ClientID: clientID.String(),
EdgeAddrs: []string{},
Region: "",
EdgeIPVersion: allregions.Auto, // Default is ipv4
EdgeBindAddr: nil, // default is to let cf handle it
HAConnections: 4, // 4 is default
IsAutoupdated: false,
LBPool: "", // looks interesting?
Tags: []pogs.Tag{},
Log: logTransport,
LogTransport: logTransport,
Observer: observer,
ReportedVersion: "embedded-go-test",
Retries: 5, // retries, default is 5
RunFromTerminal: true, // todo false
NamedTunnel: tunnel,
ProtocolSelector: protocolSelector,
EdgeTLSConfigs: edgeTLSConfigs,
FeatureSelector: &features.FeatureSelector{}, // I think this should be OK
MaxEdgeAddrRetries: 8, // max-edge-addr-retries, default is 8
RPCTimeout: 5 * time.Second, // rpc-timeout, default is 5s
WriteStreamTimeout: time.Second * 0,
DisableQUICPathMTUDiscovery: false,
QUICConnectionLevelFlowControlLimit: 30 * (1 << 20), // default is 30MB
QUICStreamLevelFlowControlLimit: 6 * (1 << 20), // default is 6MB
ICMPRouterServer: nil,
}
supervisor.StartTunnelDaemon(ctx, tunnelConfig, orchestrator, connectedSignal, reconnectCh, graceShutdownC)
}
type QuickTunnelResponse struct {
Success bool
Result QuickTunnel
Errors []QuickTunnelError
}
type QuickTunnelError struct {
Code int
Message string
}
type QuickTunnel struct {
ID string `json:"id"`
Name string `json:"name"`
Hostname string `json:"hostname"`
AccountTag string `json:"account_tag"`
Secret []byte `json:"secret"`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment