On a patché le kernel !

Laissez-nous vous conter l'histoire de trois développeurs qui ont rencontré un problème réseau...

Florian Forestier

  • Consultant Zenika ;
  • Enseignant vacataire ISIMA ;
  • Board Clermont'ech ;
  • Speaker & "bloggeur" ;
  • OSS contributor.

Louis Leseur

  • Concepteur-Développeur @ BeYs Cloud ;
  • Plus gros consommateur EDF de la région ;
  • OSS contributor.

Au début, tout allait bien

  • Un beau matin de novembre ; au bureau avec l'équipe
  • L'équipe, parlons-en !
    • Trois développeurs ;
    • Golang / Svelte + "DevOps"
    • 4-6 ans d'XP
    • => Pas trop le style à bidouiller le kernel

Quand soudain, un manager

"Ça marche pas !"

"J'ai des serveurs qui viennent d'arriver, et le réseau y marche po"




Euh... Ok 🤨

"Ça marche pas !"

  • C'est le manager du SOC ;
  • Qui a reçu des serveurs pour la PKI ;
  • Qui vient pour un problème réseau ;
  • Et qui demande à des devs de résoudre ça.


Bon, en vrai, y'a du sens

  • La PKI a appelé au secours le SOC ;
  • Qui a demandé à l'équipe réseau (qui a pas trouvé) ;
  • Du coup, nous sommes le dernier espoir.

On récupère le matos

  • HP ProLiant DL380 Gen 10 ;
    • 700Go de RAM ;
    • 20To de disque ;
    • 2 * 24 cores (Intel)
    • Et notre réseau : HPE QLogic FastLinQ 45000 (x2)


Ah, et y'en a 8 comme ça. Aucun ne fonctionne.

(Bon, si, mais uniquement sur la carte d'admin en 1Gbps, c'est pas pratique)

On regarde le BIOS...

... Et on voit les cartes réseau ! 🎉

"Tranquille, c'est juste un driver" !


On installe une Debian 12

  • Assez logiquement, ça marche pas
  • On voit bien les cartes dans lspci, mais pas avec ip link
  • On récupère les erreurs dans dmesg

[    9.526144] QLogic FastLinQ 4xxxx Core Module qed
[    9.531195] qede init: QLogic FastLinQ 4xxxx Ethernet Driver qede
[    9.583755] [qed_mcp_nvm_info_populate:3374()]Failed getting number of images
[    9.583758] [qed_hw_prepare_single:4722()]Failed to populate nvm info shadow
[    9.583761] [qed_probe:513()]hw prepare failed
            

Et on apprend des choses

  • Nos cartes sont bien reconnues par le kernel ! (mais ça coupe après)
  • Visiblement, qed et qede sont les drivers responsables de ces cartes ;
  • Chaque carte devrait apparaître comme 4 cartes de 25Gbps qui forment au total 100GBps ;
  • Et le driver plante à l'initialisation de la carte.

Il doit exister un système driver

  • On se met donc à chercher un driver pour nos cartes sur Internet ;
  • On trouve des drivers chez HPE...
  • ... Qui sont juste des repackaging de qed/qede...
  • ... Sur de vieilles versions du Kernel (5.x)...
  • ... Et uniquement pour RHEL et SUSE ! 🥳🔫

Bon, ça va pas nous aider. Autre idée ?

Ça reste intéressant :

  • On apprend que nos HPE QLogic ne sont que des rebranding !
  • En fait, ce sont des Marvell QLogic
  • Et les drivers sont pour RHEL 7 et SUSE 12 (donc "vieux")
  • => Les drivers du kernel doivent marcher en natif, sinon il y aurait eu du support !
  • Serait-ce un bug de la distribution Debian ?

Du coup, on installe plein de trucs !

  • Ubuntu (24.04) car "y'a tous les drivers de la terre dessus" => Nope.
  • CentOS (Stream) car "C'est juste RHEL rebadgé et instable" => Nope.
  • Alpine Linux (3.18) car "On sait jamais, sur un malentendu, ça peut passer..." => Nope.

Et là, l'envie d'abandonner.

Rien ne marche, et il n'existe aucun driver disponible.


On retourne à nos sujets habituels

  • On conserve un serveur sur un coin de bureau, au cas où une idée surgisse.
  • D'autres tentent, et se cassent les dents aussi.

Et soudain, l'idée à la con 💡

(et c'est moi qui l'ai eu, eheh)



"Hey, si on installe cette vieille Ubuntu 18, ça donne quoi ?"

"Mais Florian c'est totalement idiot, on casse jamais* la compatibilité ascendante dans Linux tu le sais tr... Oh, ça marche"

Ubuntu 18 => Tout droit !


root@ubuntu-18-lts:~# ip a
[...]
6: ens2f0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
7: ens2f1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
8: ens2f2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
9: ens2f3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
[...]
            

Et là, on hésite entre la joie et l'envie de pleurer

  • Ça marche !
  • Mais sur un OS qui date d'il y à des années.

Faire n'importe quoi fonctionne, continuons !

  • On récupère la version du kernel de notre Ubuntu 18 (4.15) ;
  • Et on le relance notre Debian 12 sur le 4.15.
  • Et... ça marche. 👀
  • Aller, juste pour être sûr, on recompile un 4.15 depuis les sources... Ça marche aussi !

Jazz music stops

  • Le bug est dans le kernel, quelque part.
  • Mais comment le trouver ?
    • Ubuntu 18 : Kernel 4.15
    • Debian 12 : Kernel 6.1
  • Il y a 4 ans de commits sur le projet le plus gros du monde !
(

C'est quoi, le Kernel ?

  • Le Kernel, c'est un gros morceau de code qui gère les fondements du système :
    • Allocation mémoire, "aiguillage" des instructions processeur, réseau, etc
    • C'est du très bas niveau, c'est du C, c'est un projet énorme
    • Quand on parle de distribution Linux, on parle d'un système d'exploitation qui utilise ce Kernel
  • Créé par Linus Torvalds en 1991, open-sourcé en 1992
  • C'était le chainon manquant à la communauté Open-Source pour faire un OS complet (👋 Hurd)

Mais le Kernel c'est pas un OS à lui seul !

  • Pour utiliser le kernel, il faut un ensemble de logiciels utilitaires "de base" (ls, cat, more, tail, etc)
  • Il en existe plusieurs, notamment :
    • GNU-CoreUtils (Debian, RHEL, SUSE)
    • BSD (FreeBSD)
    • BusyBox (Alpine, OpenWRT)

Et ça, ça fait (à peu près) un OS !

  • Mais un OS pas giga utilisable.
  • Il manque les bibliothéques système (glibc notamment), un gestionnaire de paquet, de daemon, de fenêtres, etc
  • => On créé des distributions, qui packagent tout ça : Debian, Ubuntu, Alpine Linux, etc

Donc, quand on utilise Debian, en vrai :

  • On utilise la distribution Debian ;
  • Qui utilise les utilitaires GNU ;
  • Avec la bibliothèque standard glibc ;
  • Avec le kernel Linux.

Ne dites plus Debian mais Debian GNU/Linux !

(d'ailleurs, il existe une variante Debian GNU/Hurd)

)

Deux questions :

  • Quelle partie du kernel casse ?
  • En quelle version le breaking change apparaît ?

Quelle partie du kernel casse ?


[    9.526144] QLogic FastLinQ 4xxxx Core Module qed
[    9.531195] qede init: QLogic FastLinQ 4xxxx Ethernet Driver qede
[    9.583755] [qed_mcp_nvm_info_populate:3374()]Failed getting number of images
[    9.583758] [qed_hw_prepare_single:4722()]Failed to populate nvm info shadow
[    9.583761] [qed_probe:513()]hw prepare failed
            
  • Le driver qui plante semble être qed sur une invocation de qede

Euh, attend, qed et qede ?

  • Ce sont des drivers intégrés au kernel qui gèrent plein de cartes réseaux.
    • qed contrôle directement le firmware, les interruptions système, etc ;
    • qede expose les interfaces Ethernet de nos cartes ;
  • Mais ils ont plein d'autres copains !
    • qedr contrôle la convergence des 4 cartes Ethernet incluses dans notre carte PCI en un seul port optique (infiniband) ;
    • qedi permet de gérer iSCSI (dans notre cas, aucun usage) ;
    • qedf permet de gérer FCoE (idem).

C'est quand que ça a foiré ?

  • Name & Shame
  • Essayer de trouver l'endroit exact pour identifier la source du bug
  • => Et ainsi faciliter le fix !

C'est quand que ça a foiré ?

  • Entre la 4.15 et la 6.1, il y a 28 versions (hors version "fix")
  • Idée : On monte de LTS en LTS, et on voit où ça casse.
    • Une LTS => Plusieurs années de support. Non-LTS : 6 mois.

  • => La LTS après la 4.15, c'est la 4.19

4.19 sur Debian 12 -> Fail !

  • Notre bug est donc entre la 4.15 et la 4.19 !
  • On retente en 4.17 ?

4.17 sur Debian 12 -> Fail !

  • C'est quelque part entre la 4.15 et la 4.17
  • Aller, en 4.16 pour être sûr !

4.16 sur Debian 12 -> It works !

  • On est donc quelque part sur les commits entre la 4.16 et la 4.17 !
  • Bon, y'a plus qu'à analyser les commits entre ces deux versions !

Et on trouve assez vite !

  • Le commit 43645ce rajoute la fonction qed_mcp_nvm_info_populate
  • Et c'est cette méthode qui semble poser soucis d'après les logs :

[    9.526144] QLogic FastLinQ 4xxxx Core Module qed
[    9.531195] qede init: QLogic FastLinQ 4xxxx Ethernet Driver qede
[    9.583755] [qed_mcp_nvm_info_populate:3374()]Failed getting number of images <== ICI !
[    9.583758] [qed_hw_prepare_single:4722()]Failed to populate nvm info shadow
[    9.583761] [qed_probe:513()]hw prepare failed
            

Pour être sûr, on recompile

  • Avec le commit 43645ce => Ça plante ;
  • Avec le commit 50bc60c (juste avant) => Ça fonctionne !

Et c'est parti pour le fix

  • On regarde la fonction qui nous fait mal : qed_mcp_nvm_info_populate
  • Grâce au log, on identifie sa localisation

/* Acquire from MFW the amount of available images */
nvm_info.num_images = 0;
rc = qed_mcp_bist_nvm_get_num_images(p_hwfn, p_ptt, &nvm_info.num_images);
if (rc == -EOPNOTSUPP) {
	DP_INFO(p_hwfn, "DRV_MSG_CODE_BIST_TEST is not supported\n");
	goto out;
} else if (rc || !nvm_info.num_images) {
	DP_ERR(p_hwfn, "Failed getting number of images\n");
	goto err0;
}
            

Et c'est parti pour le fix


int qed_mcp_bist_nvm_get_num_images(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt, u32 *num_images) {
	u32 drv_mb_param = 0, rsp;
	int rc = 0;

	drv_mb_param = (DRV_MB_PARAM_BIST_NVM_TEST_NUM_IMAGES << DRV_MB_PARAM_BIST_TEST_INDEX_SHIFT);
	rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_BIST_TEST, drv_mb_param, &rsp, num_images);
	if (rc)
		return rc;
	if (((rsp & FW_MSG_CODE_MASK) != FW_MSG_CODE_OK))
		rc = -EINVAL;

	return rc;
}
            

Et c'est parti pour le fix

  • Comme on a deux possibilités, on rajoute deux lignes de log pour savoir où on échoue (print("papate"))
  • On vient de trouver la ligne fautive ! Maintenant, il faut comprendre pourquoi.
  • => Notre serveur, il échoue avec quel code d'erreur ?

Et c'est parti pour le fix

  • On recompile en ajoutant un log qui affiche la résultat de notre addition booléenne rsp & FW_MSG_CODE_MASK
  • => Le code affiché est 0x00000000
  • Grâce à un peu de logique booléenne et au fichier qed_mfw_hsi.h on découvre que le code...
  • ... Correspond à une opération non supportée !


Et c'est parti pour le fix

  • Pour rappel, dans qed_mcp_nvm_info_populate, nous avions ceci :

if (rc == -EOPNOTSUPP) {
	DP_INFO(p_hwfn, "DRV_MSG_CODE_BIST_TEST is not supported\n");
	goto out;
}
            

... Sauf que si personne ne renvoie EOPNOTSUPP, ça sert pas à grand chose !

Et c'est parti pour le fix

  • On rajoute donc un simple check dans qed_mcp_bist_nvm_get_num_images :

int qed_mcp_bist_nvm_get_num_images(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt, u32 *num_images) {
	u32 drv_mb_param = 0, rsp;
[...]

	rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_BIST_TEST, drv_mb_param, &rsp, num_images);
	if (rc)
		return rc;

+	if (((rsp & FW_MSG_CODE_MASK) == FW_MSG_CODE_UNSUPPORTED)) // <= Là :-)
+		rc = -EOPNOTSUPP;
~	else if (((rsp & FW_MSG_CODE_MASK) != FW_MSG_CODE_OK))
		rc = -EINVAL;
	return rc;
} 

On recompile...

  • Version 6.12 (latest), avec notre fix...

Et ça marche !

On a sauvé nos serveurs ! 🎉

  • On soumet alors une modification dans le kernel pour faire profiter à tout le monde
  • Et on découvre le kernel lore

Kernel lore

  • C'est l'historique complet du Kernel, sous forme de mailing-list !
  • Comme le kernel est trop gros pour être un projet Git standard, les changes sont gérés par mail :
    • On envoie nos changes avec un descriptif complet ;
    • En respectant à la lettre un ensemble de règles de soumissions ;
    • Les réponses servent de peer-review / relecture ;
    • Et à la fin, un mainteneur effectue le merge sur la code-base

Kernel lore


Et au bout de quelques semaines...


  • Notre soumission est approuvée, et nous arrivons dans le code ! (en 6.13)
  • Pour anecdote, ce fix va être backporté jusqu'en LTS 5.4 (2019) !

Pour conclure...

  • On a eu de la chance dans notre debug.
  • Linux, c'est super cool ; et le fait qu'il soit open-source encore plus !
  • Aucun système n'est infaillible, même le meilleur de tous.
  • Pour une fois, c'était pas la faute du réseau 🫣
  • Si trois gobelins y sont arrivés, vous pouvez vous aussi ! Contribuez à l'OSS. ❤️
  • Merci à Christophe d'avoir rendu tout ça possible.

Merci !