From 094aa824298b97d60a45538bb37f95cdebc408b0 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 11 Mar 2025 17:22:03 +0100 Subject: [PATCH 01/22] client proxy is routing traffic to orchestrator proxy instead of session proxy --- packages/client-proxy/main.go | 15 ++++++------ packages/orchestrator/internal/proxy/proxy.go | 0 .../internal/proxy/proxy_browser_502.html | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 packages/orchestrator/internal/proxy/proxy.go create mode 100644 packages/orchestrator/internal/proxy/proxy_browser_502.html diff --git a/packages/client-proxy/main.go b/packages/client-proxy/main.go index a517403932..c19dd24e15 100644 --- a/packages/client-proxy/main.go +++ b/packages/client-proxy/main.go @@ -28,12 +28,13 @@ import ( ) const ( - ServiceName = "client-proxy" - dnsServer = "api.service.consul:5353" - healthCheckPort = 3001 - port = 3002 - sandboxPort = 3003 - maxRetries = 3 + ServiceName = "client-proxy" + dnsServer = "api.service.consul:5353" + healthCheckPort = 3001 + port = 3002 + sandboxPort = 3003 // legacy session proxy port + sandboxProxyPort = 5007 // orchestrator proxy port + maxRetries = 3 ) var commitSHA string @@ -111,7 +112,7 @@ func proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http zap.L().Debug("Proxying request", zap.String("sandbox_id", sandboxID), zap.String("node", node)) targetUrl := &url.URL{ Scheme: "http", - Host: fmt.Sprintf("%s:%d", node, sandboxPort), + Host: fmt.Sprintf("%s:%d", node, sandboxProxyPort), } // Proxy the request diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/orchestrator/internal/proxy/proxy_browser_502.html b/packages/orchestrator/internal/proxy/proxy_browser_502.html new file mode 100644 index 0000000000..644ff07332 --- /dev/null +++ b/packages/orchestrator/internal/proxy/proxy_browser_502.html @@ -0,0 +1,23 @@ + + + + + Closed Port Error + + + +
+ +
+

Closed Port Error

+

The sandbox $dbk_session_id is running but there's no service running on port $dbk_port.

+
+
+ $host +
Connection refused on port $dbk_port
+
+

Please ensure that your service is properly configured and running on the specified port.

+ Check the sandbox logs for more information → +
+ + \ No newline at end of file From 42baa911e33049fd1589da8138400ed3a83de993 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 11 Mar 2025 17:22:23 +0100 Subject: [PATCH 02/22] added proxy port for orchestrator --- packages/nomad/main.tf | 1 + packages/nomad/orchestrator.hcl | 7 ++++++- packages/nomad/variables.tf | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/nomad/main.tf b/packages/nomad/main.tf index 7a0ee6d75f..b41f9c06fd 100644 --- a/packages/nomad/main.tf +++ b/packages/nomad/main.tf @@ -324,6 +324,7 @@ resource "nomad_job" "orchestrator" { jobspec = templatefile("${path.module}/orchestrator.hcl", { gcp_zone = var.gcp_zone port = var.orchestrator_port + proxy_port = var.orchestrator_proxy_port environment = var.environment consul_acl_token = var.consul_acl_token_secret diff --git a/packages/nomad/orchestrator.hcl b/packages/nomad/orchestrator.hcl index 6cf4f49fec..cfc882b593 100644 --- a/packages/nomad/orchestrator.hcl +++ b/packages/nomad/orchestrator.hcl @@ -25,6 +25,11 @@ job "orchestrator" { } } + service { + name = "orchestrator-proxy" + port = "${proxy_port}" + } + task "start" { driver = "raw_exec" @@ -45,7 +50,7 @@ job "orchestrator" { config { command = "/bin/bash" - args = ["-c", " chmod +x local/orchestrator && local/orchestrator --port ${port}"] + args = ["-c", " chmod +x local/orchestrator && local/orchestrator --port ${port} --proxy-port ${proxy_port}"] } artifact { diff --git a/packages/nomad/variables.tf b/packages/nomad/variables.tf index caed5d6a7a..353b5fa1b3 100644 --- a/packages/nomad/variables.tf +++ b/packages/nomad/variables.tf @@ -185,6 +185,10 @@ variable "orchestrator_port" { type = number } +variable "orchestrator_proxy_port" { + type = number +} + variable "fc_env_pipeline_bucket_name" { type = string } From 4fb1da710b11101dd4e7c67304f9dc256ec2fbfb Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 11 Mar 2025 17:23:36 +0100 Subject: [PATCH 03/22] orchestrator session http proxy --- packages/orchestrator/internal/proxy/proxy.go | 184 ++++++++++++++++++ .../internal/proxy/proxy_browser_502.html | 6 +- 2 files changed, 187 insertions(+), 3 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index e69de29bb2..23c342388b 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -0,0 +1,184 @@ +package proxy + +import ( + "context" + _ "embed" + "encoding/json" + "fmt" + "github.com/e2b-dev/infra/packages/shared/pkg/meters" + "github.com/e2b-dev/infra/packages/shared/pkg/smap" + "go.uber.org/zap" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "strings" + "time" +) + +//go:embed proxy_browser_502.html +var proxyBrowser502PageHtml string + +var browserIdentityKeywords = []string{ + "mozilla", "chrome", "safari", "firefox", "edge", "opera", "msie", +} + +type SessionProxy struct { + sandboxes *smap.Map[string] + server *http.Server +} + +func New(port uint) *SessionProxy { + server := &http.Server{Addr: fmt.Sprintf(":%d", port)} + + return &SessionProxy{ + server: server, + sandboxes: smap.New[string](), + } +} + +func (p *SessionProxy) AddSandbox(sandboxID, ip string) { + p.sandboxes.Insert(sandboxID, ip) +} + +func (p *SessionProxy) RemoveSandbox(sandboxID string) { + p.sandboxes.Remove(sandboxID) +} + +func (p *SessionProxy) Start() error { + // similar values to our old the nginx configuration + serverTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + MaxIdleConns: 1024, // Matches worker_connections + MaxIdleConnsPerHost: 8192, // Matches keepalive_requests + IdleConnTimeout: 620 * time.Second, // Matches keepalive_timeout + TLSHandshakeTimeout: 10 * time.Second, // Similar to client_header_timeout + ResponseHeaderTimeout: 24 * time.Hour, // Matches proxy_read_timeout + DisableKeepAlives: false, // Allow keep-alive + } + + p.server.Handler = http.HandlerFunc(p.proxyHandler(serverTransport)) + return p.server.ListenAndServe() +} + +func (p *SessionProxy) Shutdown(ctx context.Context) { + err := p.server.Shutdown(ctx) + if err != nil { + zap.L().Error("failed to shutdown proxy server", zap.Error(err)) + } +} + +func (p *SessionProxy) proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http.Request) { + activeConnections, err := meters.GetUpDownCounter(meters.ActiveConnectionsCounterMeterName) + if err != nil { + zap.L().Error("failed to create active connections counter", zap.Error(err)) + } + + return func(w http.ResponseWriter, r *http.Request) { + if activeConnections != nil { + activeConnections.Add(r.Context(), 1) + defer func() { + activeConnections.Add(r.Context(), -1) + }() + } + + // Extract sandbox id from the host (--.e2b.dev) + hostSplit := strings.Split(r.Host, "-") + if len(hostSplit) < 2 { + zap.L().Warn("invalid host to proxy", zap.String("host", r.Host)) + http.Error(w, "Invalid host", http.StatusBadRequest) + return + } + + sandboxID := hostSplit[1] + sandboxPortRaw := hostSplit[0] + sandboxPort, sandboxPortErr := strconv.ParseUint(sandboxPortRaw, 10, 64) + if sandboxPortErr != nil { + zap.L().Warn("invalid sandbox port", zap.String("sandbox_port", sandboxPortRaw)) + http.Error(w, "Invalid sandbox port", http.StatusBadRequest) + } + + sbxIp, sbxFound := p.sandboxes.Get(sandboxID) + if !sbxFound { + zap.L().Warn("sandbox not found", zap.String("sandbox_id", sandboxID)) + http.Error(w, "Sandbox not found", http.StatusNotFound) + return + } + + logger := zap.L().With(zap.String("sandbox_id", sandboxID), zap.String("sandbox_ip", sbxIp), zap.Uint64("sandbox_req_port", sandboxPort), zap.String("sandbox_port_path", r.URL.Path)) + + // We've resolved the node to proxy the request to + logger.Debug("Proxying request") + targetUrl := &url.URL{ + Scheme: "http", + Host: fmt.Sprintf("%s:%d", sbxIp, sandboxPort), + } + + // Proxy the request + proxy := httputil.NewSingleHostReverseProxy(targetUrl) + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + logger.Error("Reverse proxy error") + + if p.isBrowser(r.UserAgent()) { + w.WriteHeader(http.StatusBadGateway) + w.Header().Add("Content-Type", "text/html") + w.Write(p.buildHtmlClosedPortError(sandboxID, r.Host, sandboxPort)) + return + } + + w.WriteHeader(http.StatusBadGateway) + w.Header().Add("Content-Type", "application/json") + w.Write(p.buildJsonClosedPortError(sandboxID, sandboxPort)) + } + + proxy.ModifyResponse = func(resp *http.Response) error { + if resp.StatusCode >= 500 { + logger.Error("Backend responded with error", zap.Int("status_code", resp.StatusCode)) + } else { + logger.Info("Backend responded", zap.Int("status_code", resp.StatusCode)) + } + + return nil + } + + proxy.Transport = transport + proxy.ServeHTTP(w, r) + } +} + +func (p *SessionProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) []byte { + replacements := map[string]string{ + "{{sandbox_id}}": sandboxId, + "{{sandbox_port}}": strconv.FormatUint(port, 10), + "{{sandbox_host}}": host, + } + + adjustedErrTemplate := proxyBrowser502PageHtml + for placeholder, value := range replacements { + adjustedErrTemplate = strings.ReplaceAll(adjustedErrTemplate, placeholder, value) + } + + return []byte(adjustedErrTemplate) +} + +func (p *SessionProxy) buildJsonClosedPortError(sandboxId string, port uint64) []byte { + response := map[string]interface{}{ + "error": "The sandbox is running but port is not open", + "sandbox_id": sandboxId, + "port": port, + } + + responseBytes, _ := json.Marshal(response) + return responseBytes +} + +func (p *SessionProxy) isBrowser(userAgent string) bool { + userAgent = strings.ToLower(userAgent) + for _, keyword := range browserIdentityKeywords { + if strings.Contains(userAgent, keyword) { + return true + } + } + + return false +} diff --git a/packages/orchestrator/internal/proxy/proxy_browser_502.html b/packages/orchestrator/internal/proxy/proxy_browser_502.html index 644ff07332..c4579a4339 100644 --- a/packages/orchestrator/internal/proxy/proxy_browser_502.html +++ b/packages/orchestrator/internal/proxy/proxy_browser_502.html @@ -10,11 +10,11 @@

Closed Port Error

-

The sandbox $dbk_session_id is running but there's no service running on port $dbk_port.

+

The sandbox {{sandbox_id}} is running but there's no service running on port {{sandbox_port}}.

- $host -
Connection refused on port $dbk_port
+ {{sandbox_host}} +
Connection refused on port {{sandbox_port}}

Please ensure that your service is properly configured and running on the specified port.

Check the sandbox logs for more information → From 670c9fcbf61e0659b54da6d0403bc6114d80f187 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 11 Mar 2025 17:24:13 +0100 Subject: [PATCH 04/22] port number for orchestrator proxy in nomad --- main.tf | 1 + variables.tf | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/main.tf b/main.tf index 2c1388c53c..b6ddc56192 100644 --- a/main.tf +++ b/main.tf @@ -219,6 +219,7 @@ module "nomad" { # Orchestrator orchestrator_port = var.orchestrator_port + orchestrator_proxy_port = var.orchestrator_proxy_port fc_env_pipeline_bucket_name = module.buckets.fc_env_pipeline_bucket_name # Template manager diff --git a/variables.tf b/variables.tf index e6268ffd3d..307d5fb482 100644 --- a/variables.tf +++ b/variables.tf @@ -159,6 +159,11 @@ variable "orchestrator_port" { default = 5008 } +variable "orchestrator_proxy_port" { + type = number + default = 5007 +} + variable "template_manager_port" { type = number default = 5009 From a8cf105bd0e4316e3abd8a98d178da45edbe0af2 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 11 Mar 2025 17:25:47 +0100 Subject: [PATCH 05/22] usage of session proxy inside orchestrator --- .../orchestrator/cmd/mock-sandbox/mock.go | 3 ++ .../orchestrator/cmd/mock-snapshot/mock.go | 6 ++- .../internal/sandbox/sandbox_linux.go | 4 ++ .../internal/sandbox/sandbox_other.go | 2 + packages/orchestrator/internal/server/main.go | 7 +++- .../orchestrator/internal/server/sandboxes.go | 2 + packages/orchestrator/main.go | 40 +++++++++++++++++-- 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/packages/orchestrator/cmd/mock-sandbox/mock.go b/packages/orchestrator/cmd/mock-sandbox/mock.go index b983801ba6..2742cd1d90 100644 --- a/packages/orchestrator/cmd/mock-sandbox/mock.go +++ b/packages/orchestrator/cmd/mock-sandbox/mock.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "log" "os" "os/signal" @@ -104,6 +105,7 @@ func mockSandbox( buildId, sandboxId string, dns *dns.DNS, + proxy *proxy.SessionProxy, keepAlive time.Duration, networkPool *network.Pool, templateCache *template.Cache, @@ -128,6 +130,7 @@ func mockSandbox( childCtx, tracer, dns, + proxy, networkPool, templateCache, &orchestrator.SandboxConfig{ diff --git a/packages/orchestrator/cmd/mock-snapshot/mock.go b/packages/orchestrator/cmd/mock-snapshot/mock.go index 4b25a60ded..c14ad983f5 100644 --- a/packages/orchestrator/cmd/mock-snapshot/mock.go +++ b/packages/orchestrator/cmd/mock-snapshot/mock.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "log" "os" "os/signal" @@ -113,6 +114,7 @@ func mockSnapshot( buildId, sandboxId string, dns *dns.DNS, + proxy *proxy.SessionProxy, keepAlive time.Duration, networkPool *network.Pool, templateCache *template.Cache, @@ -137,6 +139,7 @@ func mockSnapshot( childCtx, tracer, dns, + proxy, networkPool, templateCache, &orchestrator.SandboxConfig{ @@ -234,7 +237,7 @@ func mockSnapshot( return fmt.Errorf("failed to add snapshot to template cache: %w", err) } - fmt.Println("Add snapshot to template cache time: ", time.Since(snapshotTime).Milliseconds()) + fmt.Println("AddSandbox snapshot to template cache time: ", time.Since(snapshotTime).Milliseconds()) start = time.Now() @@ -242,6 +245,7 @@ func mockSnapshot( childCtx, tracer, dns, + proxy, networkPool, templateCache, &orchestrator.SandboxConfig{ diff --git a/packages/orchestrator/internal/sandbox/sandbox_linux.go b/packages/orchestrator/internal/sandbox/sandbox_linux.go index 35b7618737..9f4ea6cf30 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_linux.go +++ b/packages/orchestrator/internal/sandbox/sandbox_linux.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "net/http" "os" "sync/atomic" @@ -86,6 +87,7 @@ func NewSandbox( ctx context.Context, tracer trace.Tracer, dns *dns.DNS, + proxy *proxy.SessionProxy, networkPool *network.Pool, templateCache *template.Cache, config *orchestrator.SandboxConfig, @@ -314,11 +316,13 @@ func NewSandbox( sbx.StartedAt = time.Now() dns.Add(config.SandboxId, ips.HostIP()) + proxy.AddSandbox(config.SandboxId, ips.HostIP()) telemetry.ReportEvent(childCtx, "added DNS record", attribute.String("ip", ips.HostIP()), attribute.String("hostname", config.SandboxId)) cleanup.Add(func() error { dns.Remove(config.SandboxId, ips.HostIP()) + proxy.RemoveSandbox(config.SandboxId) return nil }) diff --git a/packages/orchestrator/internal/sandbox/sandbox_other.go b/packages/orchestrator/internal/sandbox/sandbox_other.go index 2390318800..3e639f5ca1 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_other.go +++ b/packages/orchestrator/internal/sandbox/sandbox_other.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "net/http" "sync/atomic" "time" @@ -74,6 +75,7 @@ func NewSandbox( ctx context.Context, tracer trace.Tracer, dns *dns.DNS, + proxy *proxy.SessionProxy, networkPool *network.Pool, templateCache *template.Cache, config *orchestrator.SandboxConfig, diff --git a/packages/orchestrator/internal/server/main.go b/packages/orchestrator/internal/server/main.go index c71460633c..44e07296a8 100644 --- a/packages/orchestrator/internal/server/main.go +++ b/packages/orchestrator/internal/server/main.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "math" "net" "os" @@ -38,6 +39,7 @@ type server struct { orchestrator.UnimplementedSandboxServiceServer sandboxes *smap.Map[*sandbox.Sandbox] dns *dns.DNS + proxy *proxy.SessionProxy tracer trace.Tracer networkPool *network.Pool templateCache *template.Cache @@ -54,6 +56,7 @@ type Service struct { server *server grpc *grpc.Server dns *dns.DNS + proxy *proxy.SessionProxy port uint16 shutdown struct { once sync.Once @@ -68,7 +71,7 @@ type Service struct { useClickhouseMetrics string } -func New(ctx context.Context, port uint, clientID string) (*Service, error) { +func New(ctx context.Context, port uint, clientID string, proxy *proxy.SessionProxy) (*Service, error) { if port > math.MaxUint16 { return nil, fmt.Errorf("%d is larger than maximum possible port %d", port, math.MaxInt16) } @@ -92,6 +95,7 @@ func New(ctx context.Context, port uint, clientID string) (*Service, error) { // BLOCK: initialize services { srv.dns = dns.New() + srv.proxy = proxy opts := []logging.Option{ logging.WithLogOnEvents(logging.StartCall, logging.PayloadReceived, logging.PayloadSent, logging.FinishCall), @@ -143,6 +147,7 @@ func New(ctx context.Context, port uint, clientID string) (*Service, error) { srv.server = &server{ tracer: otel.Tracer(ServiceName), dns: srv.dns, + proxy: srv.proxy, sandboxes: smap.New[*sandbox.Sandbox](), networkPool: networkPool, templateCache: templateCache, diff --git a/packages/orchestrator/internal/server/sandboxes.go b/packages/orchestrator/internal/server/sandboxes.go index 5c33189abb..909b62360a 100644 --- a/packages/orchestrator/internal/server/sandboxes.go +++ b/packages/orchestrator/internal/server/sandboxes.go @@ -49,6 +49,7 @@ func (s *server) Create(ctxConn context.Context, req *orchestrator.SandboxCreate childCtx, s.tracer, s.dns, + s.proxy, s.networkPool, s.templateCache, req.Sandbox, @@ -182,6 +183,7 @@ func (s *server) Delete(ctxConn context.Context, in *orchestrator.SandboxDeleteR // Don't allow connecting to the sandbox anymore. s.dns.Remove(in.SandboxId, sbx.Slot.HostIP()) + s.proxy.RemoveSandbox(in.SandboxId) // Remove the sandbox from the cache to prevent loading it again in API during the time the instance is stopping. // Old comment: diff --git a/packages/orchestrator/main.go b/packages/orchestrator/main.go index 400fe5375d..4596702d78 100644 --- a/packages/orchestrator/main.go +++ b/packages/orchestrator/main.go @@ -5,12 +5,14 @@ import ( "errors" "flag" "fmt" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "log" "os" "os/signal" "sync" "sync/atomic" "syscall" + "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -24,8 +26,9 @@ import ( ) const ( - defaultPort = 5008 - ServiceName = "orchestrator" + defaultPort = 5008 + defaultProxyPort = 5007 + ServiceName = "orchestrator" ) var commitSHA string @@ -38,8 +41,10 @@ func main() { defer sigCancel() var port uint - flag.UintVar(&port, "port", defaultPort, "orchestrator server port") + + var proxyPort uint + flag.UintVar(&proxyPort, "proxy-port", defaultProxyPort, "orchestrator proxy port") flag.Parse() wg := &sync.WaitGroup{} @@ -97,8 +102,9 @@ func main() { log.Println("Starting orchestrator", "commit", commitSHA) clientID := consul.GetClientID() + sessionProxy := proxy.New(proxyPort) - srv, err := server.New(ctx, port, clientID) + srv, err := server.New(ctx, port, clientID, sessionProxy) if err != nil { zap.L().Fatal("failed to create server", zap.Error(err)) } @@ -149,6 +155,32 @@ func main() { } }() + wg.Add(1) + go func() { + defer wg.Done() + + errChan := make(chan error, 1) + go func() { + err := sessionProxy.Start() + errChan <- err + }() + + select { + case <-ctx.Done(): + case <-errChan: + if err != nil { + zap.L().Error("session proxy failed", zap.Error(err)) + exitCode.Add(1) + cancel() + } + } + + // close session proxy, wait 5 seconds until all connections are closed + shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer shutdownCtxCancel() + defer sessionProxy.Shutdown(shutdownCtx) + }() + wg.Wait() os.Exit(int(exitCode.Load())) From bae57a16ec91c523523f01c99a5390fccf48a294 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 11 Mar 2025 17:34:29 +0100 Subject: [PATCH 06/22] sorted imports, missing proxy arg in test mocks --- packages/orchestrator/cmd/mock-sandbox/mock.go | 5 ++++- packages/orchestrator/cmd/mock-snapshot/mock.go | 4 +++- packages/orchestrator/internal/sandbox/sandbox_linux.go | 2 +- packages/orchestrator/internal/sandbox/sandbox_other.go | 6 +++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/orchestrator/cmd/mock-sandbox/mock.go b/packages/orchestrator/cmd/mock-sandbox/mock.go index 2742cd1d90..602a192ad7 100644 --- a/packages/orchestrator/cmd/mock-sandbox/mock.go +++ b/packages/orchestrator/cmd/mock-sandbox/mock.go @@ -4,7 +4,6 @@ import ( "context" "flag" "fmt" - "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "log" "os" "os/signal" @@ -14,6 +13,7 @@ import ( "go.opentelemetry.io/otel" "github.com/e2b-dev/infra/packages/orchestrator/internal/dns" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/nbd" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/network" @@ -52,6 +52,8 @@ func main() { }() dnsServer := dns.New() + proxyServer := proxy.New(3333) + go func() { log.Printf("Starting DNS server") @@ -88,6 +90,7 @@ func main() { *buildId, *sandboxId+"-"+strconv.Itoa(v), dnsServer, + proxyServer, time.Duration(*keepAlive)*time.Second, networkPool, templateCache, diff --git a/packages/orchestrator/cmd/mock-snapshot/mock.go b/packages/orchestrator/cmd/mock-snapshot/mock.go index c14ad983f5..ef90d88c36 100644 --- a/packages/orchestrator/cmd/mock-snapshot/mock.go +++ b/packages/orchestrator/cmd/mock-snapshot/mock.go @@ -4,7 +4,6 @@ import ( "context" "flag" "fmt" - "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "log" "os" "os/signal" @@ -15,6 +14,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/e2b-dev/infra/packages/orchestrator/internal/dns" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/nbd" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/network" @@ -52,6 +52,7 @@ func main() { cancel() }() + proxyServer := proxy.New(3333) dnsServer := dns.New() go func() { log.Printf("Starting DNS server") @@ -91,6 +92,7 @@ func main() { *buildId, *sandboxId+"-"+strconv.Itoa(v), dnsServer, + proxyServer, time.Duration(*keepAlive)*time.Second, networkPool, templateCache, diff --git a/packages/orchestrator/internal/sandbox/sandbox_linux.go b/packages/orchestrator/internal/sandbox/sandbox_linux.go index 9f4ea6cf30..cd85b8c969 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_linux.go +++ b/packages/orchestrator/internal/sandbox/sandbox_linux.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "net/http" "os" "sync/atomic" @@ -22,6 +21,7 @@ import ( "golang.org/x/sys/unix" "github.com/e2b-dev/infra/packages/orchestrator/internal/dns" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/build" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/fc" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/nbd" diff --git a/packages/orchestrator/internal/sandbox/sandbox_other.go b/packages/orchestrator/internal/sandbox/sandbox_other.go index 3e639f5ca1..74e9b5303f 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_other.go +++ b/packages/orchestrator/internal/sandbox/sandbox_other.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "net/http" "sync/atomic" "time" @@ -15,6 +14,7 @@ import ( "go.opentelemetry.io/otel/trace" "github.com/e2b-dev/infra/packages/orchestrator/internal/dns" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/build" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/nbd" "github.com/e2b-dev/infra/packages/orchestrator/internal/sandbox/network" @@ -69,8 +69,8 @@ func (s *Sandbox) LoggerMetadata() sbxlogger.SandboxMetadata { // Run cleanup functions for the already initialized resources if there is any error or after you are done with the started sandbox. func NewSandbox( - // YOU ARE IN SANDBOX_OTHER.GO - // YOU PROBABLY WANT TO BE IN SANDBOX_LINUX.GO +// YOU ARE IN SANDBOX_OTHER.GO +// YOU PROBABLY WANT TO BE IN SANDBOX_LINUX.GO ctx context.Context, tracer trace.Tracer, From fed98ccb48e1992d0a2018b3e570cb5a1ee3084f Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 12 Mar 2025 11:07:51 +0100 Subject: [PATCH 07/22] renamed var for orchestrator proxy port --- packages/client-proxy/main.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/client-proxy/main.go b/packages/client-proxy/main.go index c19dd24e15..cdbdf3a2cc 100644 --- a/packages/client-proxy/main.go +++ b/packages/client-proxy/main.go @@ -28,13 +28,13 @@ import ( ) const ( - ServiceName = "client-proxy" - dnsServer = "api.service.consul:5353" - healthCheckPort = 3001 - port = 3002 - sandboxPort = 3003 // legacy session proxy port - sandboxProxyPort = 5007 // orchestrator proxy port - maxRetries = 3 + ServiceName = "client-proxy" + dnsServer = "api.service.consul:5353" + healthCheckPort = 3001 + port = 3002 + sandboxPort = 3003 // legacy session proxy port + orchestratorProxyPort = 5007 // orchestrator proxy port + maxRetries = 3 ) var commitSHA string @@ -112,7 +112,7 @@ func proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http zap.L().Debug("Proxying request", zap.String("sandbox_id", sandboxID), zap.String("node", node)) targetUrl := &url.URL{ Scheme: "http", - Host: fmt.Sprintf("%s:%d", node, sandboxProxyPort), + Host: fmt.Sprintf("%s:%d", node, orchestratorProxyPort), } // Proxy the request From 8ab9969d8d02362a4fd5d65a58a3b81fdbff5a27 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 12 Mar 2025 11:09:35 +0100 Subject: [PATCH 08/22] orchestrator: renamed session proxy to sandbox proxy --- .../orchestrator/cmd/mock-sandbox/mock.go | 2 +- .../orchestrator/cmd/mock-snapshot/mock.go | 2 +- packages/orchestrator/internal/proxy/proxy.go | 22 +++++++++---------- .../internal/sandbox/sandbox_linux.go | 2 +- .../internal/sandbox/sandbox_other.go | 6 ++--- packages/orchestrator/internal/server/main.go | 6 ++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/orchestrator/cmd/mock-sandbox/mock.go b/packages/orchestrator/cmd/mock-sandbox/mock.go index 602a192ad7..3d76dbefac 100644 --- a/packages/orchestrator/cmd/mock-sandbox/mock.go +++ b/packages/orchestrator/cmd/mock-sandbox/mock.go @@ -108,7 +108,7 @@ func mockSandbox( buildId, sandboxId string, dns *dns.DNS, - proxy *proxy.SessionProxy, + proxy *proxy.SandboxProxy, keepAlive time.Duration, networkPool *network.Pool, templateCache *template.Cache, diff --git a/packages/orchestrator/cmd/mock-snapshot/mock.go b/packages/orchestrator/cmd/mock-snapshot/mock.go index ef90d88c36..3d0d521922 100644 --- a/packages/orchestrator/cmd/mock-snapshot/mock.go +++ b/packages/orchestrator/cmd/mock-snapshot/mock.go @@ -116,7 +116,7 @@ func mockSnapshot( buildId, sandboxId string, dns *dns.DNS, - proxy *proxy.SessionProxy, + proxy *proxy.SandboxProxy, keepAlive time.Duration, networkPool *network.Pool, templateCache *template.Cache, diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index 23c342388b..06f6c8b87a 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -23,29 +23,29 @@ var browserIdentityKeywords = []string{ "mozilla", "chrome", "safari", "firefox", "edge", "opera", "msie", } -type SessionProxy struct { +type SandboxProxy struct { sandboxes *smap.Map[string] server *http.Server } -func New(port uint) *SessionProxy { +func New(port uint) *SandboxProxy { server := &http.Server{Addr: fmt.Sprintf(":%d", port)} - return &SessionProxy{ + return &SandboxProxy{ server: server, sandboxes: smap.New[string](), } } -func (p *SessionProxy) AddSandbox(sandboxID, ip string) { +func (p *SandboxProxy) AddSandbox(sandboxID, ip string) { p.sandboxes.Insert(sandboxID, ip) } -func (p *SessionProxy) RemoveSandbox(sandboxID string) { +func (p *SandboxProxy) RemoveSandbox(sandboxID string) { p.sandboxes.Remove(sandboxID) } -func (p *SessionProxy) Start() error { +func (p *SandboxProxy) Start() error { // similar values to our old the nginx configuration serverTransport := &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -61,14 +61,14 @@ func (p *SessionProxy) Start() error { return p.server.ListenAndServe() } -func (p *SessionProxy) Shutdown(ctx context.Context) { +func (p *SandboxProxy) Shutdown(ctx context.Context) { err := p.server.Shutdown(ctx) if err != nil { zap.L().Error("failed to shutdown proxy server", zap.Error(err)) } } -func (p *SessionProxy) proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http.Request) { +func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http.Request) { activeConnections, err := meters.GetUpDownCounter(meters.ActiveConnectionsCounterMeterName) if err != nil { zap.L().Error("failed to create active connections counter", zap.Error(err)) @@ -146,7 +146,7 @@ func (p *SessionProxy) proxyHandler(transport *http.Transport) func(w http.Respo } } -func (p *SessionProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) []byte { +func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) []byte { replacements := map[string]string{ "{{sandbox_id}}": sandboxId, "{{sandbox_port}}": strconv.FormatUint(port, 10), @@ -161,7 +161,7 @@ func (p *SessionProxy) buildHtmlClosedPortError(sandboxId string, host string, p return []byte(adjustedErrTemplate) } -func (p *SessionProxy) buildJsonClosedPortError(sandboxId string, port uint64) []byte { +func (p *SandboxProxy) buildJsonClosedPortError(sandboxId string, port uint64) []byte { response := map[string]interface{}{ "error": "The sandbox is running but port is not open", "sandbox_id": sandboxId, @@ -172,7 +172,7 @@ func (p *SessionProxy) buildJsonClosedPortError(sandboxId string, port uint64) [ return responseBytes } -func (p *SessionProxy) isBrowser(userAgent string) bool { +func (p *SandboxProxy) isBrowser(userAgent string) bool { userAgent = strings.ToLower(userAgent) for _, keyword := range browserIdentityKeywords { if strings.Contains(userAgent, keyword) { diff --git a/packages/orchestrator/internal/sandbox/sandbox_linux.go b/packages/orchestrator/internal/sandbox/sandbox_linux.go index cd85b8c969..24623c984e 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_linux.go +++ b/packages/orchestrator/internal/sandbox/sandbox_linux.go @@ -87,7 +87,7 @@ func NewSandbox( ctx context.Context, tracer trace.Tracer, dns *dns.DNS, - proxy *proxy.SessionProxy, + proxy *proxy.SandboxProxy, networkPool *network.Pool, templateCache *template.Cache, config *orchestrator.SandboxConfig, diff --git a/packages/orchestrator/internal/sandbox/sandbox_other.go b/packages/orchestrator/internal/sandbox/sandbox_other.go index 74e9b5303f..25374a9565 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_other.go +++ b/packages/orchestrator/internal/sandbox/sandbox_other.go @@ -69,13 +69,13 @@ func (s *Sandbox) LoggerMetadata() sbxlogger.SandboxMetadata { // Run cleanup functions for the already initialized resources if there is any error or after you are done with the started sandbox. func NewSandbox( -// YOU ARE IN SANDBOX_OTHER.GO -// YOU PROBABLY WANT TO BE IN SANDBOX_LINUX.GO + // YOU ARE IN SANDBOX_OTHER.GO + // YOU PROBABLY WANT TO BE IN SANDBOX_LINUX.GO ctx context.Context, tracer trace.Tracer, dns *dns.DNS, - proxy *proxy.SessionProxy, + proxy *proxy.SandboxProxy, networkPool *network.Pool, templateCache *template.Cache, config *orchestrator.SandboxConfig, diff --git a/packages/orchestrator/internal/server/main.go b/packages/orchestrator/internal/server/main.go index 44e07296a8..e2eb3a57c8 100644 --- a/packages/orchestrator/internal/server/main.go +++ b/packages/orchestrator/internal/server/main.go @@ -39,7 +39,7 @@ type server struct { orchestrator.UnimplementedSandboxServiceServer sandboxes *smap.Map[*sandbox.Sandbox] dns *dns.DNS - proxy *proxy.SessionProxy + proxy *proxy.SandboxProxy tracer trace.Tracer networkPool *network.Pool templateCache *template.Cache @@ -56,7 +56,7 @@ type Service struct { server *server grpc *grpc.Server dns *dns.DNS - proxy *proxy.SessionProxy + proxy *proxy.SandboxProxy port uint16 shutdown struct { once sync.Once @@ -71,7 +71,7 @@ type Service struct { useClickhouseMetrics string } -func New(ctx context.Context, port uint, clientID string, proxy *proxy.SessionProxy) (*Service, error) { +func New(ctx context.Context, port uint, clientID string, proxy *proxy.SandboxProxy) (*Service, error) { if port > math.MaxUint16 { return nil, fmt.Errorf("%d is larger than maximum possible port %d", port, math.MaxInt16) } From 1e3060ce04ee7385008eaa0d2b7b8d2d1cad0cec Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 13 Mar 2025 12:18:05 +0100 Subject: [PATCH 09/22] simplified proxy error handling via channel --- packages/orchestrator/main.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/orchestrator/main.go b/packages/orchestrator/main.go index 4596702d78..5cf9e18be5 100644 --- a/packages/orchestrator/main.go +++ b/packages/orchestrator/main.go @@ -5,7 +5,6 @@ import ( "errors" "flag" "fmt" - "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "log" "os" "os/signal" @@ -18,6 +17,7 @@ import ( "go.uber.org/zap/zapcore" "github.com/e2b-dev/infra/packages/orchestrator/internal/consul" + "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" "github.com/e2b-dev/infra/packages/orchestrator/internal/server" "github.com/e2b-dev/infra/packages/shared/pkg/env" "github.com/e2b-dev/infra/packages/shared/pkg/logger" @@ -161,13 +161,12 @@ func main() { errChan := make(chan error, 1) go func() { - err := sessionProxy.Start() - errChan <- err + errChan <- sessionProxy.Start() }() select { case <-ctx.Done(): - case <-errChan: + case err = <-errChan: if err != nil { zap.L().Error("session proxy failed", zap.Error(err)) exitCode.Add(1) From 9e0a8f3d410718e22986c632c79ef792446cf612 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 13 Mar 2025 12:19:04 +0100 Subject: [PATCH 10/22] revert traffic routing to sandbox proxy --- packages/client-proxy/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client-proxy/main.go b/packages/client-proxy/main.go index cdbdf3a2cc..8b849af9d7 100644 --- a/packages/client-proxy/main.go +++ b/packages/client-proxy/main.go @@ -112,7 +112,7 @@ func proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http zap.L().Debug("Proxying request", zap.String("sandbox_id", sandboxID), zap.String("node", node)) targetUrl := &url.URL{ Scheme: "http", - Host: fmt.Sprintf("%s:%d", node, orchestratorProxyPort), + Host: fmt.Sprintf("%s:%d", node, sandboxPort), } // Proxy the request From ab5f432bd34b779b467d81a031fd23831b9443db Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 13 Mar 2025 13:37:24 +0100 Subject: [PATCH 11/22] use html template for replacing variables --- packages/orchestrator/internal/proxy/proxy.go | 29 ++++++++++++------- .../internal/proxy/proxy_browser_502.html | 6 ++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index 06f6c8b87a..d6994c4e1f 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -1,6 +1,7 @@ package proxy import ( + "bytes" "context" _ "embed" "encoding/json" @@ -8,6 +9,7 @@ import ( "github.com/e2b-dev/infra/packages/shared/pkg/meters" "github.com/e2b-dev/infra/packages/shared/pkg/smap" "go.uber.org/zap" + "html/template" "net/http" "net/http/httputil" "net/url" @@ -120,9 +122,16 @@ func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.Respo logger.Error("Reverse proxy error") if p.isBrowser(r.UserAgent()) { + res, resErr := p.buildHtmlClosedPortError(sandboxID, r.Host, sandboxPort) + if resErr != nil { + logger.Error("Failed to build HTML error response", zap.Error(resErr)) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusBadGateway) w.Header().Add("Content-Type", "text/html") - w.Write(p.buildHtmlClosedPortError(sandboxID, r.Host, sandboxPort)) + w.Write(res) return } @@ -146,19 +155,19 @@ func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.Respo } } -func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) []byte { - replacements := map[string]string{ - "{{sandbox_id}}": sandboxId, - "{{sandbox_port}}": strconv.FormatUint(port, 10), - "{{sandbox_host}}": host, +func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) ([]byte, error) { + htmlResponse := new(bytes.Buffer) + htmlTmpl, err := template.New("template").Parse(proxyBrowser502PageHtml) + if err != nil { + return nil, err } - adjustedErrTemplate := proxyBrowser502PageHtml - for placeholder, value := range replacements { - adjustedErrTemplate = strings.ReplaceAll(adjustedErrTemplate, placeholder, value) + err = htmlTmpl.Execute(htmlResponse, map[string]string{"sandboxId": sandboxId, "sandboxHost": host, "sandboxPort": fmt.Sprintf("%d", port)}) + if err != nil { + return nil, err } - return []byte(adjustedErrTemplate) + return htmlResponse.Bytes(), nil } func (p *SandboxProxy) buildJsonClosedPortError(sandboxId string, port uint64) []byte { diff --git a/packages/orchestrator/internal/proxy/proxy_browser_502.html b/packages/orchestrator/internal/proxy/proxy_browser_502.html index c4579a4339..ead2f510bf 100644 --- a/packages/orchestrator/internal/proxy/proxy_browser_502.html +++ b/packages/orchestrator/internal/proxy/proxy_browser_502.html @@ -10,11 +10,11 @@

Closed Port Error

-

The sandbox {{sandbox_id}} is running but there's no service running on port {{sandbox_port}}.

+

The sandbox {{.sandboxId}} is running but there's no service running on port {{.sandboxPort}}.

- {{sandbox_host}} -
Connection refused on port {{sandbox_port}}
+ {{.sandboxHost}} +
Connection refused on port {{.sandboxPort}}

Please ensure that your service is properly configured and running on the specified port.

Check the sandbox logs for more information → From 13097aa7da06f7529ba0c6d74e269150673b720f Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 13 Mar 2025 14:04:30 +0100 Subject: [PATCH 12/22] proxy sandbox removal with strictly exact ip --- packages/orchestrator/internal/proxy/proxy.go | 4 ++-- packages/orchestrator/internal/sandbox/sandbox_linux.go | 2 +- packages/orchestrator/internal/server/sandboxes.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index d6994c4e1f..f82eb382bf 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -43,8 +43,8 @@ func (p *SandboxProxy) AddSandbox(sandboxID, ip string) { p.sandboxes.Insert(sandboxID, ip) } -func (p *SandboxProxy) RemoveSandbox(sandboxID string) { - p.sandboxes.Remove(sandboxID) +func (p *SandboxProxy) RemoveSandbox(sandboxID string, ip string) { + p.sandboxes.RemoveCb(sandboxID, func(k string, v string, ok bool) bool { return ok && v == ip }) } func (p *SandboxProxy) Start() error { diff --git a/packages/orchestrator/internal/sandbox/sandbox_linux.go b/packages/orchestrator/internal/sandbox/sandbox_linux.go index 24623c984e..f20df3ac67 100644 --- a/packages/orchestrator/internal/sandbox/sandbox_linux.go +++ b/packages/orchestrator/internal/sandbox/sandbox_linux.go @@ -322,7 +322,7 @@ func NewSandbox( cleanup.Add(func() error { dns.Remove(config.SandboxId, ips.HostIP()) - proxy.RemoveSandbox(config.SandboxId) + proxy.RemoveSandbox(config.SandboxId, ips.HostIP()) return nil }) diff --git a/packages/orchestrator/internal/server/sandboxes.go b/packages/orchestrator/internal/server/sandboxes.go index 909b62360a..26e63283b4 100644 --- a/packages/orchestrator/internal/server/sandboxes.go +++ b/packages/orchestrator/internal/server/sandboxes.go @@ -183,7 +183,7 @@ func (s *server) Delete(ctxConn context.Context, in *orchestrator.SandboxDeleteR // Don't allow connecting to the sandbox anymore. s.dns.Remove(in.SandboxId, sbx.Slot.HostIP()) - s.proxy.RemoveSandbox(in.SandboxId) + s.proxy.RemoveSandbox(in.SandboxId, sbx.Slot.HostIP()) // Remove the sandbox from the cache to prevent loading it again in API during the time the instance is stopping. // Old comment: From edddf82793b33d188748ab7e89271fba40a512fe Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Fri, 14 Mar 2025 15:13:58 +0100 Subject: [PATCH 13/22] fixed typo --- packages/orchestrator/cmd/mock-snapshot/mock.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/cmd/mock-snapshot/mock.go b/packages/orchestrator/cmd/mock-snapshot/mock.go index 3d0d521922..ae659bc73f 100644 --- a/packages/orchestrator/cmd/mock-snapshot/mock.go +++ b/packages/orchestrator/cmd/mock-snapshot/mock.go @@ -239,7 +239,7 @@ func mockSnapshot( return fmt.Errorf("failed to add snapshot to template cache: %w", err) } - fmt.Println("AddSandbox snapshot to template cache time: ", time.Since(snapshotTime).Milliseconds()) + fmt.Println("Add snapshot to template cache time: ", time.Since(snapshotTime).Milliseconds()) start = time.Now() From 3c22503482037cbeca0e9aeaec5d7218a2592994 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Fri, 14 Mar 2025 15:14:44 +0100 Subject: [PATCH 14/22] initialize html temp once, use type structs for variables --- packages/orchestrator/internal/proxy/proxy.go | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index f82eb382bf..b87734fa5c 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -23,6 +23,18 @@ var proxyBrowser502PageHtml string var browserIdentityKeywords = []string{ "mozilla", "chrome", "safari", "firefox", "edge", "opera", "msie", +var browserTemplate = template.Must(template.New("template").Parse(proxyBrowser502PageHtml)) + +type htmlTemplateData struct { + sandboxId string + sandboxHost string + sandboxPort string +} + +type jsonTemplateData struct { + error string + sandbox_id string + port uint64 } type SandboxProxy struct { @@ -157,12 +169,9 @@ func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.Respo func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) ([]byte, error) { htmlResponse := new(bytes.Buffer) - htmlTmpl, err := template.New("template").Parse(proxyBrowser502PageHtml) - if err != nil { - return nil, err - } + htmlVars := htmlTemplateData{sandboxId: sandboxId, sandboxHost: host, sandboxPort: strconv.FormatUint(port, 10)} - err = htmlTmpl.Execute(htmlResponse, map[string]string{"sandboxId": sandboxId, "sandboxHost": host, "sandboxPort": fmt.Sprintf("%d", port)}) + err := browserTemplate.Execute(htmlResponse, htmlVars) if err != nil { return nil, err } @@ -171,10 +180,10 @@ func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, p } func (p *SandboxProxy) buildJsonClosedPortError(sandboxId string, port uint64) []byte { - response := map[string]interface{}{ - "error": "The sandbox is running but port is not open", - "sandbox_id": sandboxId, - "port": port, + response := jsonTemplateData{ + error: "The sandbox is running but port is not open", + sandbox_id: sandboxId, + port: port, } responseBytes, _ := json.Marshal(response) From 28ab252863bd7ca823ebc84bc70021a97cd77c4f Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Fri, 14 Mar 2025 15:14:56 +0100 Subject: [PATCH 15/22] use regexp for browser user-agent matching --- packages/orchestrator/internal/proxy/proxy.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index b87734fa5c..60d8b06b77 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -13,6 +13,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "regexp" "strconv" "strings" "time" @@ -21,8 +22,7 @@ import ( //go:embed proxy_browser_502.html var proxyBrowser502PageHtml string -var browserIdentityKeywords = []string{ - "mozilla", "chrome", "safari", "firefox", "edge", "opera", "msie", +var browserRegex = regexp.MustCompile(`(?i)mozilla|chrome|safari|firefox|edge|opera|msie`) var browserTemplate = template.Must(template.New("template").Parse(proxyBrowser502PageHtml)) type htmlTemplateData struct { @@ -191,12 +191,5 @@ func (p *SandboxProxy) buildJsonClosedPortError(sandboxId string, port uint64) [ } func (p *SandboxProxy) isBrowser(userAgent string) bool { - userAgent = strings.ToLower(userAgent) - for _, keyword := range browserIdentityKeywords { - if strings.Contains(userAgent, keyword) { - return true - } - } - - return false + return browserRegex.MatchString(userAgent) } From d083756cff6806a33bf1db421f142f230381a725 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Fri, 14 Mar 2025 15:37:56 +0100 Subject: [PATCH 16/22] orchestrator proxy service shutdown without timeout specified --- packages/orchestrator/main.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/orchestrator/main.go b/packages/orchestrator/main.go index 5cf9e18be5..ffd6e4eb72 100644 --- a/packages/orchestrator/main.go +++ b/packages/orchestrator/main.go @@ -5,16 +5,14 @@ import ( "errors" "flag" "fmt" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" "log" "os" "os/signal" "sync" "sync/atomic" "syscall" - "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" "github.com/e2b-dev/infra/packages/orchestrator/internal/consul" "github.com/e2b-dev/infra/packages/orchestrator/internal/proxy" @@ -174,10 +172,8 @@ func main() { } } - // close session proxy, wait 5 seconds until all connections are closed - shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), 5*time.Second) - defer shutdownCtxCancel() - defer sessionProxy.Shutdown(shutdownCtx) + // close sandbox proxy, this will wait until all sessions are closed + defer sessionProxy.Shutdown(context.Background()) }() wg.Wait() From bb505d3c638e20da2e0d6eb2334e35178e9d097d Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Fri, 14 Mar 2025 15:40:06 +0100 Subject: [PATCH 17/22] separated metric name for orchestrator proxy --- packages/orchestrator/internal/proxy/proxy.go | 2 +- packages/shared/pkg/meters/main.go | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index 60d8b06b77..265cb2cb3f 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -83,7 +83,7 @@ func (p *SandboxProxy) Shutdown(ctx context.Context) { } func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.ResponseWriter, r *http.Request) { - activeConnections, err := meters.GetUpDownCounter(meters.ActiveConnectionsCounterMeterName) + activeConnections, err := meters.GetUpDownCounter(meters.OrchestratorProxyActiveConnectionsCounterMeterName) if err != nil { zap.L().Error("failed to create active connections counter", zap.Error(err)) } diff --git a/packages/shared/pkg/meters/main.go b/packages/shared/pkg/meters/main.go index 9fccb31f98..1a15882618 100644 --- a/packages/shared/pkg/meters/main.go +++ b/packages/shared/pkg/meters/main.go @@ -16,12 +16,13 @@ const ( type UpDownCounterType string const ( - SandboxCountMeterName UpDownCounterType = "api.env.instance.running" - BuildCounterMeterName = "api.env.build.running" - NewNetworkSlotSPoolCounterMeterName = "orchestrator.network.slots_pool.new" - ReusedNetworkSlotSPoolCounterMeterName = "orchestrator.network.slots_pool.reused" - NBDkSlotSReadyPoolCounterMeterName = "orchestrator.nbd.slots_pool.read" - ActiveConnectionsCounterMeterName = "client_proxy.connections.active" + SandboxCountMeterName UpDownCounterType = "api.env.instance.running" + BuildCounterMeterName = "api.env.build.running" + NewNetworkSlotSPoolCounterMeterName = "orchestrator.network.slots_pool.new" + ReusedNetworkSlotSPoolCounterMeterName = "orchestrator.network.slots_pool.reused" + NBDkSlotSReadyPoolCounterMeterName = "orchestrator.nbd.slots_pool.read" + ActiveConnectionsCounterMeterName = "client_proxy.connections.active" + OrchestratorProxyActiveConnectionsCounterMeterName = "orchestrator.proxy.connections.active" ) var meter = otel.GetMeterProvider().Meter("nomad") From 97245dc3a2b9781fed43b0c9954cdb449cf62120 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Mon, 17 Mar 2025 15:44:50 +0100 Subject: [PATCH 18/22] propagate upstream error into log --- packages/orchestrator/internal/proxy/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index 265cb2cb3f..a57f26ad95 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -131,7 +131,7 @@ func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.Respo // Proxy the request proxy := httputil.NewSingleHostReverseProxy(targetUrl) proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { - logger.Error("Reverse proxy error") + logger.Error("Reverse proxy error", zap.Error(err)) if p.isBrowser(r.UserAgent()) { res, resErr := p.buildHtmlClosedPortError(sandboxID, r.Host, sandboxPort) From 5ebfce5f3a6c40784ef1e82b5555b375ece74fda Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Mon, 17 Mar 2025 15:45:17 +0100 Subject: [PATCH 19/22] go template can work only with sturcts with visible fields --- packages/orchestrator/internal/proxy/proxy.go | 8 ++++---- .../orchestrator/internal/proxy/proxy_browser_502.html | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index a57f26ad95..e462b00591 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -26,9 +26,9 @@ var browserRegex = regexp.MustCompile(`(?i)mozilla|chrome|safari|firefox|edge|op var browserTemplate = template.Must(template.New("template").Parse(proxyBrowser502PageHtml)) type htmlTemplateData struct { - sandboxId string - sandboxHost string - sandboxPort string + SandboxId string + SandboxHost string + SandboxPort string } type jsonTemplateData struct { @@ -169,7 +169,7 @@ func (p *SandboxProxy) proxyHandler(transport *http.Transport) func(w http.Respo func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, port uint64) ([]byte, error) { htmlResponse := new(bytes.Buffer) - htmlVars := htmlTemplateData{sandboxId: sandboxId, sandboxHost: host, sandboxPort: strconv.FormatUint(port, 10)} + htmlVars := htmlTemplateData{SandboxId: sandboxId, SandboxHost: host, SandboxPort: strconv.FormatUint(port, 10)} err := browserTemplate.Execute(htmlResponse, htmlVars) if err != nil { diff --git a/packages/orchestrator/internal/proxy/proxy_browser_502.html b/packages/orchestrator/internal/proxy/proxy_browser_502.html index ead2f510bf..d7125f4779 100644 --- a/packages/orchestrator/internal/proxy/proxy_browser_502.html +++ b/packages/orchestrator/internal/proxy/proxy_browser_502.html @@ -10,11 +10,11 @@

Closed Port Error

-

The sandbox {{.sandboxId}} is running but there's no service running on port {{.sandboxPort}}.

+

The sandbox {{.SandboxId}} is running but there's no service running on port {{.SandboxPort}}.

- {{.sandboxHost}} -
Connection refused on port {{.sandboxPort}}
+ {{.SandboxHost}} +
Connection refused on port {{.SandboxPort}}

Please ensure that your service is properly configured and running on the specified port.

Check the sandbox logs for more information → From 6021052b9c8c1c807be746636d544058ffdcaccd Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 18 Mar 2025 11:07:10 +0100 Subject: [PATCH 20/22] fixed json serializaiton after moved from anonymous struct to named one --- packages/orchestrator/internal/proxy/proxy.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index e462b00591..fb5ca5f921 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -6,8 +6,6 @@ import ( _ "embed" "encoding/json" "fmt" - "github.com/e2b-dev/infra/packages/shared/pkg/meters" - "github.com/e2b-dev/infra/packages/shared/pkg/smap" "go.uber.org/zap" "html/template" "net/http" @@ -17,6 +15,9 @@ import ( "strconv" "strings" "time" + + "github.com/e2b-dev/infra/packages/shared/pkg/meters" + "github.com/e2b-dev/infra/packages/shared/pkg/smap" ) //go:embed proxy_browser_502.html @@ -32,9 +33,9 @@ type htmlTemplateData struct { } type jsonTemplateData struct { - error string - sandbox_id string - port uint64 + Error string `json:"error"` + SandboxId string `json:"sandbox_id"` + Port uint64 `json:"port"` } type SandboxProxy struct { @@ -181,9 +182,9 @@ func (p *SandboxProxy) buildHtmlClosedPortError(sandboxId string, host string, p func (p *SandboxProxy) buildJsonClosedPortError(sandboxId string, port uint64) []byte { response := jsonTemplateData{ - error: "The sandbox is running but port is not open", - sandbox_id: sandboxId, - port: port, + Error: "The sandbox is running but port is not open", + SandboxId: sandboxId, + Port: port, } responseBytes, _ := json.Marshal(response) From de1ccddbf4a0b9456740b39274816d683e860960 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 18 Mar 2025 15:41:45 +0100 Subject: [PATCH 21/22] disable keep alives for orchestrator proxy to match settings of current nginx based session-proxy with new implementation there is error that envd returns "connection reset by peer" when there was support for keep alive tcp connections for re-use. --- packages/orchestrator/internal/proxy/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index fb5ca5f921..48ce4dbb1b 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -69,7 +69,7 @@ func (p *SandboxProxy) Start() error { IdleConnTimeout: 620 * time.Second, // Matches keepalive_timeout TLSHandshakeTimeout: 10 * time.Second, // Similar to client_header_timeout ResponseHeaderTimeout: 24 * time.Hour, // Matches proxy_read_timeout - DisableKeepAlives: false, // Allow keep-alive + DisableKeepAlives: true, // Disable keep-alives, envd doesn't support idle connections } p.server.Handler = http.HandlerFunc(p.proxyHandler(serverTransport)) From aed10d98eef0c80a7add0a418c00d3519b9486cd Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 19 Mar 2025 18:16:16 +0100 Subject: [PATCH 22/22] fixed wrong name for sandbox id in err response --- packages/orchestrator/internal/proxy/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/internal/proxy/proxy.go b/packages/orchestrator/internal/proxy/proxy.go index 48ce4dbb1b..23fcc587c8 100644 --- a/packages/orchestrator/internal/proxy/proxy.go +++ b/packages/orchestrator/internal/proxy/proxy.go @@ -34,7 +34,7 @@ type htmlTemplateData struct { type jsonTemplateData struct { Error string `json:"error"` - SandboxId string `json:"sandbox_id"` + SandboxId string `json:"sandboxId"` Port uint64 `json:"port"` }