mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: force legacy tunnels to new version (#2914)
This commit is contained in:
@ -20,6 +20,7 @@ type Region struct {
|
|||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
RegionID int `json:"region_id"`
|
||||||
HostnameHTTPS string `json:"hostname_https"`
|
HostnameHTTPS string `json:"hostname_https"`
|
||||||
HostnameWireguard string `json:"hostname_wireguard"`
|
HostnameWireguard string `json:"hostname_wireguard"`
|
||||||
WireguardPort uint16 `json:"wireguard_port"`
|
WireguardPort uint16 `json:"wireguard_port"`
|
||||||
@ -29,11 +30,12 @@ type Node struct {
|
|||||||
|
|
||||||
var Regions = []Region{
|
var Regions = []Region{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 0,
|
||||||
LocationName: "US East Pittsburgh",
|
LocationName: "US East Pittsburgh",
|
||||||
Nodes: []Node{
|
Nodes: []Node{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
|
RegionID: 0,
|
||||||
HostnameHTTPS: "pit-1.try.coder.app",
|
HostnameHTTPS: "pit-1.try.coder.app",
|
||||||
HostnameWireguard: "pit-1.try.coder.app",
|
HostnameWireguard: "pit-1.try.coder.app",
|
||||||
WireguardPort: 55551,
|
WireguardPort: 55551,
|
||||||
|
@ -3,11 +3,9 @@ package devtunnel
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -15,7 +13,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/briandowns/spinner"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
@ -23,16 +21,10 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
|
"github.com/coder/coder/cli/cliui"
|
||||||
"github.com/coder/coder/cryptorand"
|
"github.com/coder/coder/cryptorand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
v0EndpointHTTPS = "wg-tunnel.coder.app"
|
|
||||||
|
|
||||||
v0ServerPublicKey = "+KNSMwed/IlqoesvTMSBNsHFaKVLrmmaCkn0bxIhUg0="
|
|
||||||
v0ServerIP = netip.AddrFrom16(uuid.MustParse("fcad0000-0000-4000-8000-000000000001"))
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tunnel struct {
|
type Tunnel struct {
|
||||||
URL string
|
URL string
|
||||||
Listener net.Listener
|
Listener net.Listener
|
||||||
@ -40,7 +32,6 @@ type Tunnel struct {
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
PrivateKey device.NoisePrivateKey `json:"private_key"`
|
PrivateKey device.NoisePrivateKey `json:"private_key"`
|
||||||
PublicKey device.NoisePublicKey `json:"public_key"`
|
PublicKey device.NoisePublicKey `json:"public_key"`
|
||||||
|
|
||||||
@ -48,7 +39,6 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
type configExt struct {
|
type configExt struct {
|
||||||
Version int `json:"-"`
|
Version int `json:"-"`
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
PrivateKey device.NoisePrivateKey `json:"-"`
|
PrivateKey device.NoisePrivateKey `json:"-"`
|
||||||
PublicKey device.NoisePublicKey `json:"public_key"`
|
PublicKey device.NoisePublicKey `json:"public_key"`
|
||||||
|
|
||||||
@ -146,7 +136,7 @@ func startUpdateRoutine(ctx context.Context, logger slog.Logger, cfg Config) (Se
|
|||||||
endCh := make(chan struct{})
|
endCh := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
defer close(endCh)
|
defer close(endCh)
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(10 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -179,22 +169,9 @@ func sendConfigToServer(ctx context.Context, cfg Config) (ServerResponse, error)
|
|||||||
return ServerResponse{}, xerrors.Errorf("marshal config: %w", err)
|
return ServerResponse{}, xerrors.Errorf("marshal config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var req *http.Request
|
req, err := http.NewRequestWithContext(ctx, "POST", "https://"+cfg.Tunnel.HostnameHTTPS+"/tun", bytes.NewReader(raw))
|
||||||
switch cfg.Version {
|
if err != nil {
|
||||||
case 0:
|
return ServerResponse{}, xerrors.Errorf("new request: %w", err)
|
||||||
req, err = http.NewRequestWithContext(ctx, "POST", "https://"+v0EndpointHTTPS+"/tun", bytes.NewReader(raw))
|
|
||||||
if err != nil {
|
|
||||||
return ServerResponse{}, xerrors.Errorf("new request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
req, err = http.NewRequestWithContext(ctx, "POST", "https://"+cfg.Tunnel.HostnameHTTPS+"/tun", bytes.NewReader(raw))
|
|
||||||
if err != nil {
|
|
||||||
return ServerResponse{}, xerrors.Errorf("new request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return ServerResponse{}, xerrors.Errorf("unknown config version: %d", cfg.Version)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
@ -204,23 +181,9 @@ func sendConfigToServer(ctx context.Context, cfg Config) (ServerResponse, error)
|
|||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
var resp ServerResponse
|
var resp ServerResponse
|
||||||
switch cfg.Version {
|
err = json.NewDecoder(res.Body).Decode(&resp)
|
||||||
case 0:
|
if err != nil {
|
||||||
_, _ = io.Copy(io.Discard, res.Body)
|
return ServerResponse{}, xerrors.Errorf("decode response: %w", err)
|
||||||
resp.Hostname = fmt.Sprintf("%s.%s", cfg.ID, v0EndpointHTTPS)
|
|
||||||
resp.ServerIP = v0ServerIP
|
|
||||||
resp.ServerPublicKey = encodeBase64ToHex(v0ServerPublicKey)
|
|
||||||
resp.ClientIP = netip.AddrFrom16(cfg.ID)
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
err := json.NewDecoder(res.Body).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
return ServerResponse{}, xerrors.Errorf("decode response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
_, _ = io.Copy(io.Discard, res.Body)
|
|
||||||
return ServerResponse{}, xerrors.Errorf("unknown config version: %d", cfg.Version)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
@ -273,12 +236,22 @@ func readOrGenerateConfig() (Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Version == 0 {
|
if cfg.Version == 0 {
|
||||||
cfg.Tunnel = Node{
|
_, _ = fmt.Println()
|
||||||
ID: 0,
|
_, _ = fmt.Println(cliui.Styles.Error.Render("You're running a deprecated tunnel version!"))
|
||||||
HostnameHTTPS: "wg-tunnel.coder.app",
|
_, _ = fmt.Println(cliui.Styles.Error.Render("Upgrading you to the new version now. You will need to rebuild running workspaces."))
|
||||||
HostnameWireguard: "wg-tunnel-udp.coder.app",
|
_, _ = fmt.Println()
|
||||||
WireguardPort: 55555,
|
|
||||||
|
cfg, err := GenerateConfig()
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, xerrors.Errorf("generate config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = writeConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, xerrors.Errorf("write config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
@ -291,15 +264,27 @@ func GenerateConfig() (Config, error) {
|
|||||||
}
|
}
|
||||||
pub := priv.PublicKey()
|
pub := priv.PublicKey()
|
||||||
|
|
||||||
|
spin := spinner.New(spinner.CharSets[39], 350*time.Millisecond)
|
||||||
|
spin.Suffix = " Finding the closest tunnel region..."
|
||||||
|
spin.Start()
|
||||||
|
|
||||||
node, err := FindClosestNode()
|
node, err := FindClosestNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// If we fail to find the closest node, default to US East.
|
||||||
region := Regions[0]
|
region := Regions[0]
|
||||||
n, _ := cryptorand.Intn(len(region.Nodes))
|
n, _ := cryptorand.Intn(len(region.Nodes))
|
||||||
node = region.Nodes[n]
|
node = region.Nodes[n]
|
||||||
|
spin.Stop()
|
||||||
_, _ = fmt.Println("Error picking closest dev tunnel:", err)
|
_, _ = fmt.Println("Error picking closest dev tunnel:", err)
|
||||||
_, _ = fmt.Println("Defaulting to", Regions[0].LocationName)
|
_, _ = fmt.Println("Defaulting to", Regions[0].LocationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spin.Stop()
|
||||||
|
_, _ = fmt.Printf("Using tunnel in %s with latency %s.\n",
|
||||||
|
cliui.Styles.Keyword.Render(Regions[node.RegionID].LocationName),
|
||||||
|
cliui.Styles.Code.Render(node.AvgLatency.String()),
|
||||||
|
)
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
PrivateKey: device.NoisePrivateKey(priv),
|
PrivateKey: device.NoisePrivateKey(priv),
|
||||||
@ -326,16 +311,3 @@ func writeConfig(cfg Config) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeBase64ToHex(key string) string {
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(key)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decoded) != 32 {
|
|
||||||
panic((xerrors.New("key should be 32 bytes: " + key)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return hex.EncodeToString(decoded)
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user