Skip to main content
Blog
Blog Learning

TLS-timeouts beter afhandelen in de cside Edge

Hoe cside de TLS-betrouwbaarheid in de Rust-Edge verbeterde door handshake-werk uit het Axum-accept-pad te halen en in begrensde Tokio-taken te plaatsen.

Jun 16, 2026 6 min read
Abstracte donkerblauwe visualisatie van netwerkverbindingen die door een ronde gateway stromen

Bij cside leggen we sterk de nadruk op geheugenveiligheid en prestaties. Al onze kernservices zijn geschreven in Rust, inclusief de Edge-service.

De Edge bevindt zich op een gevoelige grens. Hij moet snel, veilig en veerkrachtig zijn, want hij maakt deel uit van het pad waar we hoogwaardige signalen verzamelen voor producten zoals VPN-detectie en bot-detectie.

Voor veel webapplicaties is de eenvoudigste TLS-opzet om een cloud-load-balancer TLS te laten termineren en gewone HTTP door te sturen naar de applicatie. Dat is een goede standaardkeuze wanneer de applicatie alleen het uiteindelijke HTTP-verzoek nodig heeft.

De Edge heeft een andere taak.

Sommige detectiesignalen bestaan al voordat het verzoek gewone HTTP wordt, op laag 4 (de transportlaag) in plaats van laag 7. Als TLS volledig wordt getermineerd voordat het verkeer de Edge bereikt, zijn die signalen niet langer in dezelfde vorm beschikbaar. Voor dit deel van het platform moet de Edge daarom op laag 4 werken en het TLS-pad rechtstreeks beheren.

Dat geeft cside een betere signaalkwaliteit voor detectie, maar het betekent ook dat de Edge zelf het TLS-gedrag uit de praktijk moet afhandelen.

Dit bericht gaat over één kleine betrouwbaarheidsverbetering in dat pad: het TLS-handshake-werk uit het Axum-accept-pad halen en in begrensde Tokio-taken plaatsen.

Wat begon te falen

De patch zelf was niet groot, maar het begrijpen van de storing kostte wat uitzoekwerk.

De Edge was gezond. Certificaten werden geladen. De poort was bereikbaar. Het meeste verkeer gedroeg zich normaal.

Maar onder hogere belasting leken sommige HTTPS-checks en clientverbindingen vast te lopen of een timeout te krijgen.

In eerste instantie kan dat op een TLS-probleem lijken. In de praktijk was het belangrijke patroon specifieker: sommige clients openden een verbinding maar voltooiden de TLS-handshake niet.

Dat is normaal op een service die op het internet is gericht. Publieke eindpunten zien voortdurend onvolledige verbindingen:

client maakt verbinding en stuurt vervolgens niets
client start TLS en verdwijnt vervolgens
client start TLS en blijft vervolgens hangen

Die storingen waren niet het verrassende deel.

Het verrassende deel was hoeveel impact één onvolledige handshake kon hebben op nabijgelegen gezond verkeer.

Voor de fix

Voor de fix deed één deel van het Edge-TLS-pad te veel werk in één enkele stap.

Conceptueel gedroeg het zich zo:

accepteer één verbinding
voltooi het TLS-werk voor die verbinding
accepteer vervolgens de volgende verbinding

In vereenvoudigde Rust zag de vorm er ongeveer zo uit:

impl axum::serve::Listener for EdgeTlsListener {
  async fn accept(&mut self) -> (TlsStream, SocketAddr) {
    let (mut tcp_stream, addr) = self.tcp_listener.accept().await;

    let hello = read_tls_hello(&mut tcp_stream).await;
    let tls_stream = complete_tls_handshake(hello, tcp_stream).await;

    (tls_stream, addr)
  }
}

Die code is makkelijk te doorgronden, maar plaatst de volledige TLS-handshake binnen accept().

Voor Axum is accept() de voordeur. Als die bezig is met wachten op één verbinding, ontvangt de server niet de volgende voltooide verbinding van die listener.

Dat lijkt eenvoudig, maar het veroorzaakt head-of-line blocking.

Als één verbinding TLS startte en vervolgens bleef hangen, wachtte de Edge op de timeout van die verbinding voordat hij verderging. Tijdens dat wachten konden gezonde verbindingen erachter vertraging oplopen.

Het probleem was niet:

TLS kan niet werken

Het was:

één onvolledige TLS-handshake kan latere gezonde handshakes vertragen

Dat onderscheid deed ertoe.

Het verhogen van de timeout zou het probleem niet hebben opgelost. Het zou ervoor hebben gezorgd dat het trage pad de boel nog langer ophield.

De fix

De fix bestond eruit het accepteren van een verbinding te scheiden van het voltooien van de bijbehorende TLS-handshake.

De Edge accepteert nu snel nieuwe verbindingen en handelt elke TLS-handshake onafhankelijk af. Een vastgelopen of onvolledige handshake kan nog steeds een timeout krijgen, maar blokkeert latere gezonde verbindingen niet langer in hun voortgang.

Conceptueel ziet de nieuwe flow er zo uit:

accepteer verbindingen snel
handel elke TLS-handshake onafhankelijk af
geef alleen voltooide beveiligde verbindingen door aan de verzoekafhandeling

De nieuwe vorm gebruikt Tokio-taken plus een channel van voltooide beveiligde verbindingen:

let (ready_tx, ready_rx) = tokio::sync::mpsc::channel(limit);

tokio::spawn(async move {
  loop {
    let (tcp_stream, addr) = tcp_listener.accept().await;
    let ready_tx = ready_tx.clone();

    tokio::spawn(async move {
      if let Some(tls_stream) = finish_tls(tcp_stream).await {
        let _ = ready_tx.send((tls_stream, addr)).await;
      }
    });
  }
});

De listener die naar Axum is gericht, wordt dan veel kleiner:

impl axum::serve::Listener for EdgeTlsListener {
  async fn accept(&mut self) -> (TlsStream, SocketAddr) {
    self.ready_rx
      .recv()
      .await
      .expect("TLS accept loop terminated")
  }
}

Het belangrijke deel is de grens: accept() voert het trage handshake-werk niet langer zelf uit. Het ontvangt handshakes die al voltooid zijn.

Zo veranderde de storingsmodus van dit:

één vastgelopen handshake
-> vertraagt de volgende verbinding

naar dit:

één vastgelopen handshake
-> krijgt onafhankelijk een timeout
-> gezonde verbindingen gaan door

Dat is de belangrijke betrouwbaarheidsverbetering.

De fix heeft de timeouts niet verwijderd. Timeouts zijn nog steeds noodzakelijk. Een onvolledige handshake zou niet eeuwig moeten blijven bestaan.

De fix veranderde waar de timeout wordt betaald. Een slechte verbinding betaalt nu haar eigen timeout in plaats van andere verbindingen ervoor te laten betalen.

Het begrensd houden

Er is een tweede belangrijk deel van de fix.

Als elke nieuwe verbinding onbeperkt werk kan creëren, dan wordt de service wel responsief maar niet veilig onder druk. Daarom begrenst de Edge ook het aantal TLS-handshakes dat tegelijkertijd onderweg kan zijn.

De vereenvoudigde versie ziet er zo uit:

let permits = Arc::new(tokio::sync::Semaphore::new(limit));

loop {
  let (tcp_stream, addr) = tcp_listener.accept().await;
  let permit = permits.clone().acquire_owned().await.expect("TLS semaphore closed");
  let ready_tx = ready_tx.clone();

  tokio::spawn(async move {
    let _permit = permit;

    if let Some(tls_stream) = finish_tls(tcp_stream).await {
      let _ = ready_tx.send((tls_stream, addr)).await;
    }
  });
}

De permit is eigendom van de taak. Wanneer de taak klaar is, laat Rust de permit vallen en geeft de capaciteit terug aan de semafoor. Daardoor blijft de concurrency-grens gekoppeld aan de levensduur van het daadwerkelijke handshake-werk.

Dat geeft ons beide eigenschappen die we wilden:

trage handshakes blokkeren geen gezonde handshakes

en:

trage handshakes kunnen geen onbegrensd werk creëren

Dit is het soort afweging dat ons aan het hart gaat in de Edge: de betrouwbaarheid verbeteren zonder voorspelbaar resourcegebruik op te geven.

Storingen zichtbaar maken

We hebben ook een intern storingspad aangescherpt.

Als het deel van de Edge dat verantwoordelijk is voor het accepteren van beveiligde verbindingen ooit onverwacht stopt, zou de service niet stilletjes eeuwig moeten blijven wachten. Stille blokkades zijn lastig te beheren en lastig te doorgronden.

De channel maakt deze toestand expliciet. Als alle senders weg zijn, geeft het ontvangen uit de channel None terug. Dat zou niet als normale inactiviteit moeten worden behandeld, dus vervangt de uiteindelijke accept()-body de eerdere .expect() door een expliciete, gelogde storing:

self.ready_rx.recv().await.unwrap_or_else(|| {
  tracing::error!("TLS accept loop terminated");
  panic!("TLS accept loop terminated")
})

Het storingspad wordt nu onmiddellijk zichtbaar in plaats van te veranderen in een verborgen wachttijd.

Dat verandert niets aan normaal klantverkeer, maar het maakt het systeem makkelijker te vertrouwen tijdens incidenten.

Wat we hebben geleerd

De belangrijkste les is dat timeouts niet genoeg zijn als de timeout op de verkeerde plek wordt betaald.

Een timeout rond TLS-werk klinkt redelijk. Maar als één trage verbinding ongerelateerde verbindingen erachter kan laten wachten, wordt de timeout gedeelde pijn.

Het betere model is:

accepteer snel
isoleer traag werk
begrens de concurrency
maak onverwachte storingen zichtbaar

Een andere les is dat services die op het internet gericht zijn, onvolledige verbindingen als normaal moeten beschouwen. Clients verbreken de verbinding. Health checks proberen het opnieuw. Netwerken haperen. Sommige handshakes worden nooit voltooid.

De Edge zou er niet van uit moeten gaan dat het internet netjes is.

Voor de fix:

één onvolledige handshake
-> nabijgelegen gezond verkeer kan moeten wachten

Na de fix:

één onvolledige handshake
-> geïsoleerde timeout
-> gezond verkeer gaat door

De uiteindelijke patch liet slechte verbindingen niet verdwijnen.

Hij zorgde ervoor dat de Edge ze op de juiste plek afhandelt, met de juiste grenzen, terwijl de prestatie- en geheugenveiligheidsgaranties behouden blijven die we van onze Rust-services verwachten.

Taym Haddadi
Software Engineer

Software engineer at cside, working on the Rust services behind the cside Edge.

FAQ

Frequently Asked Questions

Sommige detectieproducten van cside leunen op signalen op verbindingsniveau die alleen beschikbaar zijn voordat een verzoek door beheerde TLS-terminatie is platgeslagen tot gewone HTTP. De Edge verwerkt dat pad rechtstreeks, zodat die signalen veilig en consistent kunnen worden gebruikt.

Nee. De TLS-afhandeling blijft binnen de Edge-service. De wijziging verbeterde de manier waarop onvolledige handshakes worden geïsoleerd, zodat ze geen invloed hebben op gezond verkeer.

Monitor en Beveilig Je Third-Party Scripts

Gain full visibility and control over every script delivered to your users to enhance site security and performance.

Start gratis, of probeer Business met een proefperiode van 14 dagen.

cside dashboard interface met script monitoring en beveiligingsanalytics
Related Articles
Boek een demo