---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# Chapitre 5 — TCP : fiabilité et contrôle de flux

TCP (Transmission Control Protocol, RFC 793) est le protocole de transport sur lequel repose la quasi-totalité des communications fiables d'Internet : HTTP, HTTPS, SSH, SMTP, FTP. Son rôle est de fournir un canal de communication **fiable, ordonné et contrôlé** au-dessus d'IP, qui lui est fondamentalement non fiable.

```{code-cell} python
:tags: [hide-input]
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patheffects as pe
import numpy as np
import pandas as pd
import struct
import socket
import threading
import time
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted")
plt.rcParams.update({
    "figure.dpi": 120,
    "font.family": "DejaVu Sans",
    "axes.spines.top": False,
    "axes.spines.right": False,
})
```

## Le segment TCP

Un segment TCP est l'unité de données de la couche transport. Son en-tête fait **20 octets** minimum (sans options).

### Structure de l'en-tête TCP

```{code-cell} python
:tags: [hide-input]
fig, ax = plt.subplots(figsize=(14, 9))
ax.set_xlim(0, 32)
ax.set_ylim(0, 11.5)
ax.axis("off")
ax.set_title("Structure de l'en-tête TCP (20 octets minimum, 32 bits par ligne)",
             fontsize=13, fontweight="bold", pad=12)

lignes_tcp = [
    (9.8, [("Port source\n16 bits",       16, "#e74c3c"),
           ("Port destination\n16 bits",  16, "#c0392b")]),
    (7.6, [("Numéro de séquence (SEQ)\n32 bits", 32, "#2980b9")]),
    (5.4, [("Numéro d'acquittement (ACK)\n32 bits", 32, "#27ae60")]),
    (3.2, [("Data\nOffset\n4 bits",   4, "#8e44ad"),
           ("Réservé\n3 bits",         3, "#9b59b6"),
           ("NS CWR ECE URG ACK PSH RST SYN FIN\n9 bits (flags)", 9, "#e67e22"),
           ("Fenêtre (RWND)\n16 bits", 16, "#f39c12")]),
    (1.0, [("Checksum\n16 bits",        16, "#16a085"),
           ("Pointeur Urgent\n16 bits", 16, "#1abc9c")]),
]

for y, champs in lignes_tcp:
    x = 0
    for label, bits, color in champs:
        width = (bits / 32) * 32
        rect = mpatches.FancyBboxPatch((x + 0.05, y + 0.05), width - 0.1, 1.7,
                                        boxstyle="round,pad=0.05",
                                        edgecolor="#444", facecolor=color, alpha=0.85, linewidth=1.2)
        ax.add_patch(rect)
        ax.text(x + width/2, y + 0.95, label, ha="center", va="center",
                fontsize=7.5, color="white", fontweight="bold")
        x += width

# Annotations drapeaux
flags_info = [
    ("SYN\nSynchronize", 23.5, 3.2, "#e67e22"),
    ("ACK\nAcknowledge", 20.0, 3.2, "#e67e22"),
    ("FIN\nFinish",      31.0, 3.2, "#e67e22"),
    ("RST\nReset",       26.5, 3.2, "#e67e22"),
    ("PSH\nPush",        17.0, 3.2, "#e67e22"),
]

# Numéros de bits
ax.text(0, 11.2, "0", ha="left", fontsize=8, color="#555")
ax.text(16, 11.2, "16", ha="center", fontsize=8, color="#555")
ax.text(32, 11.2, "31", ha="right", fontsize=8, color="#555")
ax.axhline(11.0, color="#cccccc", linewidth=0.8, linestyle="--")

plt.tight_layout()
plt.show()
```

### Description des champs

| Champ | Taille | Rôle |
|-------|--------|------|
| **Port source** | 16 bits | Port du processus émetteur (1–65535) |
| **Port destination** | 16 bits | Port du service destinataire |
| **Numéro de séquence (SEQ)** | 32 bits | Position du premier octet de ce segment dans le flux |
| **Numéro d'acquittement (ACK)** | 32 bits | SEQ du prochain octet attendu de l'autre côté |
| **Data Offset** | 4 bits | Taille de l'en-tête en mots de 32 bits (min=5 → 20 octets) |
| **Flags** | 9 bits | SYN, ACK, FIN, RST, PSH, URG, ECE, CWR, NS |
| **Fenêtre (RWND)** | 16 bits | Taille de la fenêtre de réception (contrôle de flux) |
| **Checksum** | 16 bits | Intégrité de l'en-tête + données |
| **Pointeur urgent** | 16 bits | Offset vers les données urgentes (si URG=1) |

```{code-cell} python
import struct

FLAGS_TCP = {
    "FIN": 0x001, "SYN": 0x002, "RST": 0x004, "PSH": 0x008,
    "ACK": 0x010, "URG": 0x020, "ECE": 0x040, "CWR": 0x080, "NS": 0x100,
}

def construire_segment_tcp(port_src: int, port_dst: int, seq: int, ack: int,
                             flags: list[str], fenetre: int,
                             payload: bytes = b"") -> bytes:
    """
    Construit un segment TCP (sans pseudo-en-tête pour le checksum — simplifié).
    """
    flags_val = 0
    for f in flags:
        flags_val |= FLAGS_TCP.get(f.upper(), 0)

    data_offset = 5  # 20 octets (pas d'options)
    offset_flags = (data_offset << 12) | flags_val

    en_tete = struct.pack(">HHIIHHHH",
        port_src,
        port_dst,
        seq,
        ack,
        offset_flags,
        fenetre,
        0,      # Checksum (0 = non calculé ici)
        0,      # Pointeur urgent
    )
    return en_tete + payload

def decoder_segment_tcp(data: bytes) -> dict:
    """Décode un segment TCP."""
    (port_src, port_dst, seq, ack,
     offset_flags, fenetre, checksum, urgent) = struct.unpack(">HHIIHHHH", data[:20])

    data_offset = (offset_flags >> 12) & 0x0F
    flags_val   = offset_flags & 0x1FF
    flags_actifs = [nom for nom, bit in FLAGS_TCP.items() if flags_val & bit]
    payload = data[data_offset * 4:]

    return {
        "Port source":     port_src,
        "Port destination":port_dst,
        "SEQ":             seq,
        "ACK":             ack,
        "Data Offset":     f"{data_offset} × 4 = {data_offset*4} octets",
        "Flags":           ", ".join(flags_actifs) if flags_actifs else "aucun",
        "Fenêtre":         fenetre,
        "Payload":         payload.decode("ascii", errors="replace") if payload else "(vide)",
    }

# SYN initial du client
syn = construire_segment_tcp(
    port_src=54321, port_dst=80,
    seq=1000, ack=0,
    flags=["SYN"], fenetre=65535
)
print("Segment SYN (client → serveur) :")
for k, v in decoder_segment_tcp(syn).items():
    print(f"  {k:<20} : {v}")

print()

# SYN-ACK du serveur
syn_ack = construire_segment_tcp(
    port_src=80, port_dst=54321,
    seq=5000, ack=1001,
    flags=["SYN", "ACK"], fenetre=8192
)
print("Segment SYN-ACK (serveur → client) :")
for k, v in decoder_segment_tcp(syn_ack).items():
    print(f"  {k:<20} : {v}")
```

---

## Établissement de connexion : le 3-way handshake

Avant tout échange de données, TCP établit une connexion en **3 étapes** (three-way handshake). Cela permet aux deux parties de synchroniser leurs numéros de séquence initiaux (**ISN** — Initial Sequence Number).

```{code-cell} python
:tags: [hide-input]
fig, ax = plt.subplots(figsize=(12, 9))
ax.set_xlim(0, 12)
ax.set_ylim(0, 10)
ax.axis("off")
ax.set_title("Three-Way Handshake TCP — Établissement de connexion",
             fontsize=13, fontweight="bold")

# Colonnes client / serveur
for x, label, etat in [(2, "Client", "CLOSED → SYN_SENT → ESTABLISHED"),
                        (10, "Serveur", "CLOSED → LISTEN → SYN_RCVD → ESTABLISHED")]:
    ax.add_patch(mpatches.FancyBboxPatch((x-1.2, 8.5), 2.4, 1.0,
                                          boxstyle="round,pad=0.1",
                                          edgecolor="#2c3e50", facecolor="#2c3e50", linewidth=2))
    ax.text(x, 9.0, label, ha="center", va="center",
            fontsize=12, fontweight="bold", color="white")
    ax.text(x, 8.35, etat, ha="center", va="center",
            fontsize=6.5, color="#555", fontstyle="italic")

# Lignes de vie
ax.plot([2, 2], [0.5, 8.5], color="#2c3e50", linewidth=1.5, linestyle="--", alpha=0.4)
ax.plot([10, 10], [0.5, 8.5], color="#2c3e50", linewidth=1.5, linestyle="--", alpha=0.4)

def fleche_segment(ax, x_src, x_dst, y, label, couleur, label_etat_src=None, label_etat_dst=None):
    ax.annotate("",
                xy=(x_dst, y - 0.5), xytext=(x_src, y),
                arrowprops=dict(arrowstyle="-|>", color=couleur, lw=2.5))
    xm = (x_src + x_dst) / 2
    ym = (y + y - 0.5) / 2
    ax.text(xm, ym + 0.15, label, ha="center", va="bottom", fontsize=9.5,
            color=couleur, fontweight="bold",
            bbox=dict(boxstyle="round,pad=0.3", fc="white", ec=couleur, alpha=0.9))
    if label_etat_src:
        x_off = -0.3 if x_src < x_dst else 0.3
        ax.text(x_src + x_off, y + 0.05, label_etat_src, ha="right" if x_src < x_dst else "left",
                va="center", fontsize=7.5, color=couleur, fontstyle="italic")
    if label_etat_dst:
        x_off = 0.3 if x_src < x_dst else -0.3
        ax.text(x_dst + x_off, y - 0.45, label_etat_dst, ha="left" if x_src < x_dst else "right",
                va="center", fontsize=7.5, color=couleur, fontstyle="italic")

# Étape 1 : SYN
fleche_segment(ax, 2, 10, 7.8,
               "SYN  [SEQ=1000]",
               "#e74c3c",
               label_etat_src="SYN_SENT",
               label_etat_dst="SYN_RCVD")

# Étape 2 : SYN-ACK
fleche_segment(ax, 10, 2, 6.2,
               "SYN-ACK  [SEQ=5000, ACK=1001]",
               "#2980b9",
               label_etat_dst="SYN_RCVD → ESTAB.")

# Étape 3 : ACK
fleche_segment(ax, 2, 10, 4.6,
               "ACK  [SEQ=1001, ACK=5001]",
               "#27ae60",
               label_etat_src="ESTABLISHED",
               label_etat_dst="ESTABLISHED")

# Données
fleche_segment(ax, 2, 10, 3.3,
               "DATA (HTTP GET)  [SEQ=1001]",
               "#8e44ad")

fleche_segment(ax, 10, 2, 2.0,
               "DATA (HTTP 200)  [SEQ=5001, ACK=1234]",
               "#e67e22")

# Annotations temporelles
for y, label in [(7.8, "t₁"), (6.2, "t₂"), (4.6, "t₃"), (3.3, "t₄"), (2.0, "t₅")]:
    ax.text(0.3, y, label, ha="center", va="center", fontsize=9, color="#7f8c8d")

ax.text(0.3, 8.8, "t", ha="center", fontsize=9, color="#7f8c8d", fontweight="bold")

# Légende connexion établie
ax.add_patch(mpatches.FancyBboxPatch((3.5, 4.0), 5.0, 0.4,
                                      boxstyle="round,pad=0.1",
                                      edgecolor="#27ae60", facecolor="#eafaf1",
                                      linewidth=2, linestyle="dashed"))
ax.text(6.0, 4.2, "Connexion ESTABLISHED — échange de données possible",
        ha="center", va="center", fontsize=8, color="#27ae60", fontweight="bold")

plt.tight_layout()
plt.show()
```

```{admonition} Pourquoi 3 étapes et pas 2 ?
:class: note
Avec seulement 2 étapes (SYN + SYN-ACK), le client ne confirmerait pas la réception du SYN-ACK. Le serveur ne saurait pas si le client a reçu ses paramètres (ISN notamment). Le 3e paquet (ACK) confirme que la communication bidirectionnelle est possible et que les deux ISN sont connus des deux côtés.
```

---

## Fermeture de connexion : 4-way handshake

La fermeture TCP nécessite **4 étapes** car chaque sens de la connexion doit être fermé indépendamment (la connexion est full-duplex).

```{code-cell} python
:tags: [hide-input]
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_xlim(0, 12)
ax.set_ylim(0, 9)
ax.axis("off")
ax.set_title("Fermeture TCP — 4-way handshake et état TIME_WAIT",
             fontsize=13, fontweight="bold")

for x, label in [(2, "Client (actif)"), (10, "Serveur (passif)")]:
    ax.add_patch(mpatches.FancyBboxPatch((x-1.2, 7.8), 2.4, 0.9,
                                          boxstyle="round,pad=0.1",
                                          edgecolor="#2c3e50", facecolor="#2c3e50"))
    ax.text(x, 8.25, label, ha="center", va="center",
            fontsize=11, fontweight="bold", color="white")

ax.plot([2, 2], [0.5, 7.8], color="#2c3e50", linewidth=1.5, linestyle="--", alpha=0.4)
ax.plot([10, 10], [0.5, 7.8], color="#2c3e50", linewidth=1.5, linestyle="--", alpha=0.4)

etapes = [
    (2, 10, 7.1, "FIN  [SEQ=1200]",         "#e74c3c",  "FIN_WAIT_1", "CLOSE_WAIT"),
    (10, 2, 5.7, "ACK  [ACK=1201]",          "#27ae60",  "FIN_WAIT_2", ""),
    (10, 2, 4.3, "FIN  [SEQ=5800]",          "#e67e22",  "LAST_ACK",   ""),
    (2, 10, 2.9, "ACK  [ACK=5801]",          "#2980b9",  "TIME_WAIT",  "CLOSED"),
]

for x_src, x_dst, y, label, color, etat_src, etat_dst in etapes:
    ax.annotate("", xy=(x_dst, y - 0.5), xytext=(x_src, y),
                arrowprops=dict(arrowstyle="-|>", color=color, lw=2.5))
    ax.text((x_src+x_dst)/2, (y + y-0.5)/2 + 0.12, label,
            ha="center", va="bottom", fontsize=9.5, color=color, fontweight="bold",
            bbox=dict(boxstyle="round,pad=0.3", fc="white", ec=color, alpha=0.9))
    if etat_src:
        ax.text(x_src + (-0.3 if x_src < x_dst else 0.3), y + 0.1,
                etat_src, ha="right" if x_src < x_dst else "left",
                va="center", fontsize=7.5, color=color, fontstyle="italic")
    if etat_dst:
        ax.text(x_dst + (0.3 if x_src < x_dst else -0.3), y - 0.45,
                etat_dst, ha="left" if x_src < x_dst else "right",
                va="center", fontsize=7.5, color=color, fontstyle="italic")

# TIME_WAIT
ax.add_patch(mpatches.FancyBboxPatch((0.5, 1.2), 2.8, 1.2,
                                      boxstyle="round,pad=0.1",
                                      edgecolor="#e74c3c", facecolor="#fdebd0", linewidth=2,
                                      linestyle="dashed"))
ax.text(1.9, 1.8, "TIME_WAIT\n2 × MSL ≈ 60–240 s", ha="center", va="center",
        fontsize=8, color="#e74c3c", fontweight="bold")
ax.text(1.9, 0.9, "Protège contre les\nsegments résiduels", ha="center",
        fontsize=7.5, color="#7f8c8d", fontstyle="italic")

plt.tight_layout()
plt.show()
```

### L'état TIME_WAIT

Après avoir envoyé le dernier ACK, le client entre dans l'état **TIME_WAIT** pendant **2 × MSL** (Maximum Segment Lifetime, typiquement 30–120 secondes, soit 60–240 s au total). Cela permet :

1. De s'assurer que le dernier ACK est bien arrivé (retransmission possible si le serveur renvoie un FIN)
2. D'éviter que les anciens segments d'une connexion précédente arrivent dans une nouvelle connexion sur le même quadruplet (src IP, src port, dst IP, dst port)

---

## Contrôle de flux : la fenêtre glissante

TCP garantit que l'émetteur ne sature pas le récepteur grâce au **contrôle de flux**. Le récepteur annonce sa **fenêtre de réception** (Receiver Window, RWND) dans chaque segment ACK : c'est la quantité de données qu'il peut encore recevoir en mémoire tampon.

```{code-cell} python
:tags: [hide-input]
fig, axes = plt.subplots(2, 1, figsize=(13, 10))
fig.suptitle("Fenêtre glissante TCP — Contrôle de flux", fontsize=13, fontweight="bold")

# ---- Visualisation de la fenêtre glissante ----
ax = axes[0]
ax.set_xlim(0, 20)
ax.set_ylim(-1, 3)
ax.axis("off")

segments = list(range(1, 18))  # numéros de segments

# État : [ACKed | Sent (in flight) | Window | Not yet sent]
acked     = list(range(1, 6))      # 1–5 : acquittés
in_flight = list(range(6, 10))     # 6–9 : envoyés, pas encore ACKés
fenetre   = list(range(10, 14))    # 10–13 : dans la fenêtre, pas encore envoyés
non_envoye= list(range(14, 18))    # 14+ : hors fenêtre

couleurs = {
    "acked":      ("#27ae60", "ACKé"),
    "in_flight":  ("#e67e22", "Envoyé (in-flight)"),
    "fenetre":    ("#2980b9", "Peut envoyer"),
    "non_envoye": ("#bdc3c7", "Hors fenêtre"),
}

y_seg = 1.5
w = 1.05
for i, seg in enumerate(segments):
    if seg in acked:
        color, label = "#27ae60", "acked"
    elif seg in in_flight:
        color, label = "#e67e22", "in_flight"
    elif seg in fenetre:
        color, label = "#2980b9", "fenetre"
    else:
        color, label = "#bdc3c7", "non_envoye"

    rect = mpatches.FancyBboxPatch((i * w + 0.3, y_seg - 0.4), w - 0.05, 0.8,
                                    boxstyle="round,pad=0.04",
                                    edgecolor="#555", facecolor=color, alpha=0.85, linewidth=1.2)
    ax.add_patch(rect)
    ax.text(i * w + 0.3 + (w-0.05)/2, y_seg, str(seg), ha="center", va="center",
            fontsize=9, color="white", fontweight="bold")

# Légende
legend_items = [
    ("#27ae60", "ACKé"),
    ("#e67e22", "Envoyé (ACK en attente)"),
    ("#2980b9", "Prêt à envoyer (dans RWND)"),
    ("#bdc3c7", "Hors fenêtre"),
]
for i, (color, label) in enumerate(legend_items):
    ax.add_patch(mpatches.FancyBboxPatch((0.3 + i * 4.5, 0.1), 0.6, 0.45,
                                          boxstyle="round,pad=0.04",
                                          edgecolor="#555", facecolor=color, alpha=0.85))
    ax.text(1.1 + i * 4.5, 0.32, label, va="center", fontsize=8, color="#333")

# Flèche fenêtre
ax.annotate("", xy=(13.5 * w + 0.3, y_seg + 0.6), xytext=(5.5 * w + 0.3, y_seg + 0.6),
            arrowprops=dict(arrowstyle="<->", color="#e74c3c", lw=2.0))
ax.text((5.5 + 13.5)/2 * w + 0.3, y_seg + 0.78, "Fenêtre de réception (RWND = 8 segments)",
        ha="center", va="bottom", fontsize=9, color="#e74c3c", fontweight="bold")

# Pointeur d'envoi
ax.annotate("", xy=(5.5 * w + 0.3, y_seg - 0.55), xytext=(5.5 * w + 0.3, y_seg - 0.95),
            arrowprops=dict(arrowstyle="-|>", color="#27ae60", lw=2))
ax.text(5.5 * w + 0.3, y_seg - 1.0, "SND.UNA\n(dernier ACK)", ha="center",
        fontsize=7.5, color="#27ae60")

ax.set_title("Fenêtre glissante : état courant", fontsize=10, fontweight="bold")

# ---- Évolution temporelle ----
ax2 = axes[1]
ax2.set_xlim(0, 12)
ax2.set_ylim(-0.5, 7)
ax2.set_xlabel("Temps")
ax2.set_ylabel("Numéro de séquence")
ax2.set_title("Échange de segments avec acquittements", fontsize=10, fontweight="bold")

echanges = [
    # (t_src, t_dst, seq, type, x_src, x_dst)
    (0.5, 1.5,  1, "DATA",  1.5, 10.5, "#2980b9"),
    (1.0, 2.0,  2, "DATA",  1.5, 10.5, "#2980b9"),
    (1.5, 2.5,  3, "DATA",  1.5, 10.5, "#2980b9"),
    (2.0, 2.8,  3, "ACK",  10.5,  1.5, "#27ae60"),
    (2.5, 3.3,  4, "DATA",  1.5, 10.5, "#2980b9"),
    (2.5, 3.3,  5, "DATA",  1.5, 10.5, "#e67e22"),
    (3.8, 4.5,  5, "ACK",  10.5,  1.5, "#27ae60"),
    (4.5, 5.5,  6, "DATA",  1.5, 10.5, "#2980b9"),
    (5.0, 5.8,  6, "ACK",  10.5,  1.5, "#27ae60"),
]

for t_src, t_dst, seq, typ, x_src, x_dst, color in echanges:
    ax2.annotate("", xy=(x_dst, t_dst), xytext=(x_src, t_src),
                 arrowprops=dict(arrowstyle="-|>", color=color, lw=1.8))
    ax2.text((x_src+x_dst)/2, (t_src+t_dst)/2 + 0.05,
             f"{'SEQ' if typ == 'DATA' else 'ACK'}={seq}",
             ha="center", va="bottom", fontsize=7.5, color=color)

ax2.set_yticks([])
ax2.axvline(1.5, color="#2c3e50", linewidth=2, linestyle="dashed", alpha=0.5)
ax2.axvline(10.5, color="#2c3e50", linewidth=2, linestyle="dashed", alpha=0.5)
ax2.text(1.5, 6.7, "Client", ha="center", fontsize=10, fontweight="bold", color="#2c3e50")
ax2.text(10.5, 6.7, "Serveur", ha="center", fontsize=10, fontweight="bold", color="#2c3e50")

plt.tight_layout()
plt.show()
```

### Window Scaling

Le champ RWND est sur 16 bits (max 65 535 octets). Pour les réseaux à haute bande passante et grand délai (satellites, WAN longue distance), cette limite est insuffisante. L'option **Window Scale** (RFC 7323) permet de multiplier la fenêtre par un facteur de 2^n (jusqu'à 2^14 = 16 384), portant la fenêtre effective à ~1 Go.

---

## Contrôle de congestion

Le **contrôle de flux** protège le récepteur. Le **contrôle de congestion** protège le réseau lui-même contre la surcharge. TCP adapte son débit en fonction des signes de congestion (pertes de paquets, délais).

### La fenêtre de congestion (CWND)

La quantité de données qu'un émetteur peut avoir en vol est limitée par le minimum de RWND (receiver window) et CWND (congestion window) :

$$\text{Données en vol} \leq \min(\text{RWND}, \text{CWND})$$

### Algorithmes de contrôle de congestion

**Slow Start** : Au démarrage, CWND commence à 1 MSS et double à chaque RTT (croissance exponentielle) jusqu'à atteindre le seuil ssthresh.

**Congestion Avoidance** : Quand CWND ≥ ssthresh, croissance linéaire (+1 MSS par RTT).

**Fast Retransmit** : Dès réception de 3 ACKs dupliqués, retransmission immédiate sans attendre le timeout.

**Fast Recovery** : Après Fast Retransmit, au lieu de repartir de Slow Start, CWND est réduit de moitié (pas à 1 MSS).

```{code-cell} python
:tags: [hide-input]
fig, axes = plt.subplots(1, 2, figsize=(14, 7))
fig.suptitle("Contrôle de congestion TCP", fontsize=13, fontweight="bold")

# ---- Simulation TCP Reno ----
def simuler_tcp_reno(n_rtt: int = 60) -> tuple:
    """Simule l'évolution de CWND avec TCP Reno (Slow Start + Congestion Avoidance)."""
    cwnd = 1
    ssthresh = 32
    rtts = [0]
    cwnds = [cwnd]
    ssstreshs = [ssthresh]
    phases = ["slow_start"]

    events = {20: "loss_timeout", 40: "loss_3ack"}  # Événements simulés

    for rtt in range(1, n_rtt):
        phase = phases[-1]
        event = events.get(rtt)

        if event == "loss_timeout":
            ssthresh = max(cwnd // 2, 2)
            cwnd = 1
            phases.append("slow_start")
        elif event == "loss_3ack":
            ssthresh = max(cwnd // 2, 2)
            cwnd = ssthresh  # Fast Recovery : pas de retour à 1
            phases.append("congestion_avoidance")
        else:
            if phase == "slow_start":
                cwnd = min(cwnd * 2, ssthresh + 1)
                if cwnd >= ssthresh:
                    phases.append("congestion_avoidance")
                else:
                    phases.append("slow_start")
            else:
                cwnd += 1
                phases.append("congestion_avoidance")

        rtts.append(rtt)
        cwnds.append(cwnd)
        ssstreshs.append(ssthresh)

    return rtts, cwnds, ssstreshs, phases

rtts, cwnds, ssstreshs, phases = simuler_tcp_reno(60)

ax = axes[0]
# Colorer les phases
couleurs_phases = {"slow_start": "#e74c3c", "congestion_avoidance": "#2980b9"}
for i in range(len(rtts)-1):
    phase = phases[i]
    ax.fill_between(rtts[i:i+2], 0, cwnds[i:i+2],
                    color=couleurs_phases.get(phase, "#ccc"), alpha=0.15)
    ax.plot(rtts[i:i+2], cwnds[i:i+2],
            color=couleurs_phases.get(phase, "#ccc"), linewidth=2)

ax.plot(rtts, ssstreshs, color="#f39c12", linewidth=1.5, linestyle="--",
        label="ssthresh")
ax.axvline(20, color="#e74c3c", linewidth=1.5, linestyle=":", alpha=0.7)
ax.axvline(40, color="#e67e22", linewidth=1.5, linestyle=":", alpha=0.7)
ax.text(20, max(cwnds)*0.95, "Timeout\n(loss)", ha="center", fontsize=8,
        color="#e74c3c", bbox=dict(boxstyle="round", fc="white", ec="#e74c3c", alpha=0.8))
ax.text(40, max(cwnds)*0.95, "3 ACKs dup.\n(Fast Retransmit)", ha="center", fontsize=8,
        color="#e67e22", bbox=dict(boxstyle="round", fc="white", ec="#e67e22", alpha=0.8))

# Légende manuelle des phases
ax.add_patch(mpatches.Patch(color="#e74c3c", alpha=0.4, label="Slow Start"))
ax.add_patch(mpatches.Patch(color="#2980b9", alpha=0.4, label="Congestion Avoidance"))
ax.plot([], [], color="#f39c12", linestyle="--", label="ssthresh")
ax.legend(fontsize=9)

ax.set_xlabel("Nombre de RTT")
ax.set_ylabel("CWND (MSS)")
ax.set_title("Évolution de CWND — TCP Reno")
ax.grid(True, alpha=0.3)

# ---- Comparaison Reno vs CUBIC ----
def simuler_cubic(n_rtt: int = 60) -> list:
    """Approximation simplifiée de TCP CUBIC."""
    cwnd = 1.0
    ssthresh = 32
    W_max = 0
    t_congestion = 0
    C = 0.4
    cwnds = [cwnd]

    for rtt in range(1, n_rtt):
        event = {20: "loss_timeout", 40: "loss_3ack"}.get(rtt)

        if event:
            W_max = cwnd
            ssthresh = max(cwnd * 0.7, 2)  # CUBIC : β = 0.7
            cwnd = ssthresh
            t_congestion = rtt
        else:
            if cwnd < ssthresh:
                cwnd = min(cwnd * 2, ssthresh + 1)
            else:
                # Phase CUBIC
                t = rtt - t_congestion
                W_cubic = C * (t - (W_max * 0.3 / C) ** (1/3)) ** 3 + W_max
                cwnd = max(cwnd + 1, W_cubic)

        cwnds.append(cwnd)

    return cwnds

ax2 = axes[1]
cubic_cwnds = simuler_cubic(60)
ax2.plot(rtts, cwnds, color="#e74c3c", linewidth=2, label="TCP Reno", alpha=0.85)
ax2.plot(range(len(cubic_cwnds)), cubic_cwnds, color="#2980b9", linewidth=2,
         label="TCP CUBIC", linestyle="--", alpha=0.85)
ax2.axvline(20, color="#e74c3c", linewidth=1, linestyle=":", alpha=0.5)
ax2.axvline(40, color="#e67e22", linewidth=1, linestyle=":", alpha=0.5)
ax2.legend(fontsize=10)
ax2.set_xlabel("Nombre de RTT")
ax2.set_ylabel("CWND (MSS)")
ax2.set_title("Reno vs CUBIC — comportement comparé")
ax2.grid(True, alpha=0.3)
ax2.text(0.5, 0.05,
         "CUBIC : récupération plus rapide grâce\nà une fonction cubique du temps",
         transform=ax2.transAxes, fontsize=8, color="#2980b9",
         bbox=dict(boxstyle="round", fc="white", ec="#2980b9", alpha=0.8))

plt.tight_layout()
plt.show()
```

---

## États TCP : diagramme complet

Une connexion TCP peut se trouver dans l'un des 11 états définis par la RFC 793. La machine d'états pilote les transitions en fonction des segments reçus/envoyés et des appels système.

```{code-cell} python
:tags: [hide-input]
fig, ax = plt.subplots(figsize=(13, 12))
ax.set_xlim(0, 14)
ax.set_ylim(0, 13)
ax.axis("off")
ax.set_title("Machine d'états TCP — Tous les états et transitions",
             fontsize=13, fontweight="bold")

etats = {
    "CLOSED":       (7.0, 12.0),
    "LISTEN":       (3.0,  9.5),
    "SYN_SENT":     (11.0,  9.5),
    "SYN_RCVD":     (3.0,  7.0),
    "ESTABLISHED":  (7.0,  5.0),
    "FIN_WAIT_1":   (11.0,  3.5),
    "FIN_WAIT_2":   (11.0,  1.8),
    "CLOSE_WAIT":   (3.0,  3.5),
    "LAST_ACK":     (3.0,  1.8),
    "CLOSING":      (7.0,  3.0),
    "TIME_WAIT":    (11.0,  0.3),
}

couleurs_etats = {
    "CLOSED":       "#2c3e50",
    "LISTEN":       "#8e44ad",
    "SYN_SENT":     "#e74c3c",
    "SYN_RCVD":     "#c0392b",
    "ESTABLISHED":  "#27ae60",
    "FIN_WAIT_1":   "#e67e22",
    "FIN_WAIT_2":   "#e67e22",
    "CLOSE_WAIT":   "#2980b9",
    "LAST_ACK":     "#2980b9",
    "CLOSING":      "#f39c12",
    "TIME_WAIT":    "#e74c3c",
}

# Dessiner les états
for etat, (x, y) in etats.items():
    color = couleurs_etats[etat]
    ax.add_patch(mpatches.FancyBboxPatch((x - 1.1, y - 0.35), 2.2, 0.7,
                                          boxstyle="round,pad=0.08",
                                          edgecolor=color, facecolor=color, alpha=0.85, linewidth=2))
    ax.text(x, y, etat, ha="center", va="center",
            fontsize=9, fontweight="bold", color="white")

# Transitions
transitions = [
    # (src, dst, label, couleur, position_label_offset)
    ("CLOSED",    "LISTEN",    "passive open\n(bind/listen)", "#8e44ad", (-1.0, 0.0)),
    ("CLOSED",    "SYN_SENT",  "active open\n/SYN",          "#e74c3c", (1.0, 0.0)),
    ("LISTEN",    "SYN_RCVD",  "rcv SYN\n/SYN+ACK",         "#c0392b", (0.0, -0.3)),
    ("SYN_SENT",  "ESTABLISHED","rcv SYN+ACK\n/ACK",         "#27ae60", (0.5, 0.5)),
    ("SYN_RCVD",  "ESTABLISHED","rcv ACK",                    "#27ae60", (0.5, 0.5)),
    ("ESTABLISHED","FIN_WAIT_1","close\n/FIN",                "#e67e22", (1.0, 0.0)),
    ("ESTABLISHED","CLOSE_WAIT","rcv FIN\n/ACK",              "#2980b9", (-1.0, 0.0)),
    ("FIN_WAIT_1","FIN_WAIT_2","rcv ACK",                     "#e67e22", (0.5, 0.0)),
    ("FIN_WAIT_1","CLOSING",   "rcv FIN\n/ACK",               "#f39c12", (-0.5, 0.0)),
    ("FIN_WAIT_2","TIME_WAIT", "rcv FIN\n/ACK",               "#e74c3c", (0.5, 0.0)),
    ("CLOSE_WAIT","LAST_ACK",  "close\n/FIN",                 "#2980b9", (0.0, -0.3)),
    ("LAST_ACK",  "CLOSED",    "rcv ACK",                     "#2c3e50", (-1.0, 0.5)),
    ("CLOSING",   "TIME_WAIT", "rcv ACK",                     "#f39c12", (0.5, 0.0)),
    ("TIME_WAIT", "CLOSED",    "timeout\n(2×MSL)",            "#e74c3c", (0.5, 0.0)),
]

for src, dst, label, color, offset in transitions:
    x1, y1 = etats[src]
    x2, y2 = etats[dst]
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle="-|>", color=color, lw=1.8,
                               connectionstyle="arc3,rad=0.1"))
    xm = (x1 + x2) / 2 + offset[0]
    ym = (y1 + y2) / 2 + offset[1]
    ax.text(xm, ym, label, ha="center", va="center", fontsize=6.5,
            color=color, fontstyle="italic",
            bbox=dict(boxstyle="round,pad=0.15", fc="white", ec=color, alpha=0.8))

# Légende
ax.text(0.3, 0.8, "États client (initiateur)", fontsize=8.5, color="#e74c3c", fontweight="bold")
ax.text(0.3, 0.4, "États serveur (passif)", fontsize=8.5, color="#2980b9", fontweight="bold")
ax.text(0.3, 0.0, "État normal d'échange", fontsize=8.5, color="#27ae60", fontweight="bold")

plt.tight_layout()
plt.show()
```

---

## Code Python : socket TCP client/serveur

```{code-cell} python
import socket
import threading
import time

def serveur_tcp(hote: str = "127.0.0.1", port: int = 9999,
                messages_recus: list = None):
    """Serveur TCP simple : reçoit et renvoie les messages en majuscules."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as srv:
        srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        srv.bind((hote, port))
        srv.listen(1)
        srv.settimeout(3.0)
        print(f"[Serveur] En écoute sur {hote}:{port}...")

        try:
            conn, addr = srv.accept()
            with conn:
                print(f"[Serveur] Connexion de {addr}")
                while True:
                    data = conn.recv(1024)
                    if not data:
                        break
                    message = data.decode("utf-8")
                    reponse = message.upper()
                    print(f"[Serveur] Reçu : {message!r} → Renvoi : {reponse!r}")
                    conn.sendall(reponse.encode("utf-8"))
                    if messages_recus is not None:
                        messages_recus.append(message)
        except socket.timeout:
            print("[Serveur] Timeout, fermeture.")

def client_tcp(hote: str = "127.0.0.1", port: int = 9999,
               messages: list = None):
    """Client TCP simple : envoie des messages et affiche les réponses."""
    time.sleep(0.2)  # Laisse le serveur démarrer
    if messages is None:
        messages = ["bonjour", "réseau TCP", "couche transport"]

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as cli:
        cli.connect((hote, port))
        print(f"[Client] Connecté à {hote}:{port}")

        for msg in messages:
            cli.sendall(msg.encode("utf-8"))
            reponse = cli.recv(1024).decode("utf-8")
            print(f"[Client] Envoyé : {msg!r} → Reçu : {reponse!r}")
            time.sleep(0.05)

        print("[Client] Fermeture de la connexion.")

# Lancement du serveur dans un thread séparé
messages_log = []
thread_srv = threading.Thread(target=serveur_tcp,
                               kwargs={"messages_recus": messages_log},
                               daemon=True)
thread_srv.start()

# Lancement du client
client_tcp(messages=["bonjour", "réseau TCP", "couche transport", "fin"])
thread_srv.join(timeout=4)

print(f"\n[Bilan] {len(messages_log)} message(s) traité(s) par le serveur.")
```

### Inspection des options TCP

```{code-cell} python
def analyser_options_tcp(options_bytes: bytes) -> list:
    """
    Analyse les options TCP présentes dans l'espace optionnel de l'en-tête.
    Retourne une liste de dictionnaires décrivant chaque option.
    """
    options_connues = {
        0:  ("EOL",          "End of Options List"),
        1:  ("NOP",          "No-Operation"),
        2:  ("MSS",          "Maximum Segment Size"),
        3:  ("WSOPT",        "Window Scale"),
        4:  ("SACK Perm",    "SACK Permitted"),
        5:  ("SACK",         "Selective Acknowledgment"),
        8:  ("Timestamps",   "Timestamps"),
        19: ("MD5 Sig",      "TCP MD5 Signature"),
        29: ("Multipath",    "Multipath TCP"),
        30: ("TFO",          "TCP Fast Open"),
    }

    options_parsées = []
    i = 0
    while i < len(options_bytes):
        kind = options_bytes[i]
        nom, desc = options_connues.get(kind, (f"Option {kind}", "Inconnue"))

        if kind == 0:  # EOL
            options_parsées.append({"Type": 0, "Nom": nom, "Description": desc})
            break
        elif kind == 1:  # NOP
            options_parsées.append({"Type": 1, "Nom": nom, "Description": desc})
            i += 1
        else:
            if i + 1 >= len(options_bytes):
                break
            longueur = options_bytes[i + 1]
            valeur = options_bytes[i + 2: i + longueur] if longueur > 2 else b""
            valeur_hex = valeur.hex(" ") if valeur else "(vide)"
            options_parsées.append({
                "Type":        kind,
                "Nom":         nom,
                "Description": desc,
                "Longueur":    longueur,
                "Valeur (hex)":valeur_hex,
            })
            i += longueur

    return options_parsées

# Exemple : options TCP typiques d'un SYN (MSS=1460, WSOPT=7, SACK Perm, Timestamps, NOP)
options_syn = bytes([
    2, 4, 0x05, 0xB4,      # MSS = 1460
    1,                       # NOP
    3, 3, 7,                # Window Scale = 7 (×128)
    1,                       # NOP
    1,                       # NOP
    8, 10, 0x00, 0x12, 0x34, 0x56, 0x00, 0x00, 0x00, 0x00,  # Timestamps
    4, 2,                    # SACK Permitted
])

print("Options TCP d'un segment SYN typique :")
print("-" * 55)
for opt in analyser_options_tcp(options_syn):
    print(f"  Type {opt['Type']:>3} ({opt['Nom']:<12}) : {opt.get('Description', '')}", end="")
    if "Valeur (hex)" in opt:
        print(f" | valeur = {opt['Valeur (hex)']}", end="")
    print()
```

---

## Résumé

```{code-cell} python
:tags: [hide-input]
fig, ax = plt.subplots(figsize=(13, 7))
ax.axis("off")

data = {
    "Mécanisme": [
        "3-way handshake",
        "4-way close + TIME_WAIT",
        "Numéros de séquence",
        "Numéros d'acquittement",
        "Fenêtre glissante (RWND)",
        "Slow Start",
        "Congestion Avoidance",
        "Fast Retransmit",
        "TCP CUBIC",
    ],
    "Rôle": [
        "Établissement de la connexion, synchronisation ISN",
        "Fermeture ordonnée, protection contre segments résiduels",
        "Ordonnancement et détection des pertes",
        "Confirmation cumulative des octets reçus",
        "Contrôle de flux : empêche de saturer le récepteur",
        "Démarrage lent : CWND double par RTT jusqu'à ssthresh",
        "CWND += 1 MSS par RTT une fois ssthresh atteint",
        "Retransmission sur 3 ACKs dupliqués, sans attendre le timeout",
        "Récupération cubique après congestion (Linux défaut depuis 2.6.19)",
    ],
    "Signal/Valeur": [
        "SYN → SYN-ACK → ACK",
        "FIN → ACK → FIN → ACK, 2×MSL ≈ 60–240 s",
        "32 bits, ISN aléatoire pour sécurité",
        "ACK = prochain octet attendu",
        "max 65 535 o (×128 avec Window Scale)",
        "CWND = 1 MSS → doublé chaque RTT",
        "Croissance linéaire +1 MSS/RTT",
        "ssthresh = CWND/2, CWND = ssthresh",
        "β = 0.7 (vs 0.5 pour Reno), standard Linux",
    ],
}

df = pd.DataFrame(data)
tbl = ax.table(cellText=df.values, colLabels=df.columns,
               cellLoc="left", loc="center",
               colWidths=[0.22, 0.45, 0.33])
tbl.auto_set_font_size(False)
tbl.set_fontsize(8.0)
tbl.scale(1, 2.05)

for (row, col), cell in tbl.get_celld().items():
    if row == 0:
        cell.set_facecolor("#2c3e50")
        cell.set_text_props(color="white", fontweight="bold")
    elif row % 2 == 0:
        cell.set_facecolor("#eaf4fb")
    else:
        cell.set_facecolor("white")
    cell.set_edgecolor("#cccccc")

ax.set_title("Récapitulatif du chapitre 5 — TCP", fontsize=13, fontweight="bold", pad=15)
plt.tight_layout()
plt.show()
```

```{admonition} Points clés à retenir
:class: note
- L'**en-tête TCP** contient les ports, les numéros SEQ/ACK, les flags et la taille de fenêtre RWND.
- Le **3-way handshake** (SYN / SYN-ACK / ACK) établit la connexion et synchronise les ISN.
- La **fermeture** est un 4-way (FIN / ACK / FIN / ACK) suivi d'un état **TIME_WAIT** de 2×MSL.
- La **fenêtre glissante** (RWND) contrôle le flux pour protéger le récepteur.
- Le contrôle de **congestion** (CWND) protège le réseau via Slow Start, Congestion Avoidance et Fast Retransmit.
- **TCP CUBIC** (Linux) récupère plus vite après une congestion qu'un TCP Reno classique.
- La machine d'états TCP définit 11 états (CLOSED, LISTEN, SYN_SENT, ESTABLISHED, TIME_WAIT…).
```
