Interface de requête OID synchrone dans NDIS 6.80

Les pilotes réseau Windows utilisent des requêtes OID pour envoyer des messages de contrôle dans la pile de liaisonS NDIS. Les pilotes de protocole, tels que TCPIP ou vSwitch, s’appuient sur des dizaines d’OID pour configurer chaque fonctionnalité du pilote de carte réseau sous-jacente. Avant Windows 10, version 1709, les requêtes OID étaient envoyées de deux manières : régulière et directe.

Cette rubrique présente un troisième style d’appel OID : Synchronous. Un appel synchrone est censé être à faible latence, non bloquant, évolutif et fiable. L’interface de requête OID synchrone est disponible à partir de NDIS 6.80, qui est inclus dans Windows 10, version 1709 et ultérieure.

Comparaison avec les requêtes OID régulières et directes

Avec les requêtes OID synchrones, la charge utile de l’appel (l’OID lui-même) est exactement la même qu’avec les requêtes OID régulières et directes. La seule différence réside dans l’appel lui-même. Par conséquent, ce qui est le même pour les trois types d’OID; seulement le comment est différent.

Le tableau suivant décrit les différences entre les OID standard, les OID directs et les OID synchrones.

Attribut OID standard Direct OID OID synchrone
Payload NDIS_OID_REQUEST NDIS_OID_REQUEST NDIS_OID_REQUEST
Types OID Stats, Query, Set, Method Stats, Query, Set, Method Stats, Query, Set, Method
Peut être émis par Protocoles, filtres Protocoles, filtres Protocoles, filtres
Peut être complété par Miniports, filtres Miniports, filtres Miniports, filtres
Les filtres peuvent modifier Oui Oui Oui
NDIS alloue de la mémoire Pour chaque filtre (clone OID) Pour chaque filtre (clone OID) Uniquement si un nombre inhabituellement élevé de filtres (contexte d’appel)
Peut pender Oui Oui Non
Peut bloquer Oui Non Non
IRQL == PASSIF <= DISPATCH <= DISPATCH
Sérialisé par NDIS Oui Non Non
Les filtres sont appelés Recursively Recursively Itérative
Les filtres clonent l’OID Oui Oui Non

Filtrage

Comme les deux autres types d’appels OID, les pilotes de filtre ont un contrôle total sur la requête OID dans un appel synchrone. Les pilotes de filtre peuvent observer, intercepter, modifier et émettre des OID synchrones. Toutefois, par souci d’efficacité, les mécanismes d’un OID synchrone sont quelque peu différents.

Passthrough, interception et origination

D’un point de vue conceptuel, toutes les requêtes OID sont émises à partir d’un pilote supérieur et sont effectuées par un pilote inférieur. En cours de route, la requête OID peut passer par n’importe quel nombre de pilotes de filtre.

Dans le cas le plus courant, un pilote de protocole émet une requête OID et tous les filtres passent simplement la requête OID vers le bas, sans modification. La figure suivante illustre ce scénario courant.

Le chemin d’accès OID classique provient d’un protocole.

Toutefois, tout module de filtre est autorisé à intercepter la requête OID et à la terminer. Dans ce cas, la requête ne passe pas à des pilotes inférieurs, comme illustré dans le diagramme suivant.

Le chemin d’accès OID classique provient d’un protocole et est intercepté par un filtre.

Dans certains cas, un module de filtre peut décider de créer sa propre demande OID. Cette requête commence au niveau du module de filtre et ne traverse que les pilotes inférieurs, comme le montre le diagramme suivant.

Le chemin d’accès OID classique provient d’un filtre.

Toutes les requêtes OID ont ce flux de base : un pilote supérieur (un pilote de protocole ou de filtre) émet une requête et un pilote inférieur (miniport ou pilote de filtre) l’exécute.

Fonctionnement des requêtes OID régulières et directes

Les requêtes OID régulières ou directes sont distribuées de manière récursive. Le diagramme suivant montre la séquence d’appels de fonction. Notez que la séquence elle-même ressemble beaucoup à la séquence décrite dans les diagrammes de la section précédente, mais qu’elle est organisée pour montrer la nature récursive des requêtes.

Séquence d’appels de fonction pour les requêtes OID régulières et directes.

S’il y a suffisamment de filtres installés, NDIS sera obligé d’allouer une nouvelle pile de threads pour continuer à se répéter plus profondément.

NDIS considère qu’une structure NDIS_OID_REQUEST est valide pour un seul tronçon le long de la pile. Si un pilote de filtre souhaite transmettre la requête au pilote inférieur suivant (ce qui est le cas pour la grande majorité des OID), le pilote de filtre doit insérer plusieurs dizaines de lignes de code réutilisable pour cloner la requête OID. Ce réutilisable présente plusieurs problèmes :

  1. Il force une allocation de mémoire à cloner l’OID. L’accès au pool de mémoires est à la fois lent et il est impossible de garantir la progression de la requête OID.
  2. La conception de la structure OID doit rester la même au fil du temps, car tous les pilotes de filtre codent en dur les mécanismes de copie du contenu d’un NDIS_OID_REQUEST vers un autre.
  3. Exiger tant de réutilisables masque ce que fait vraiment le filtre.

Modèle de filtrage pour les requêtes OID synchrones

Le modèle de filtrage pour les requêtes OID synchrones tire parti de la nature synchrone de l’appel pour résoudre les problèmes décrits dans la section précédente.

Gestionnaires de problèmes et complets

Contrairement aux requêtes OID régulières et directes, il existe deux hooks de filtre pour les requêtes OID synchrones : un gestionnaire de problèmes et un gestionnaire complet. Un pilote de filtre ne peut inscrire ni l’un ni l’autre, ni les deux crochets.

Les appels de problème sont appelés pour chaque pilote de filtre, en commençant du haut de la pile jusqu’au bas de la pile. Tout appel de problème de filtre peut empêcher l’OID de continuer vers le bas et terminer l’OID avec du code status. Si aucun filtre ne décide d’intercepter l’OID, l’OID atteint le pilote de carte réseau, qui doit terminer l’OID de manière synchrone.

Une fois qu’un OID est terminé, les appels terminés sont appelés pour chaque pilote de filtre, à partir de l’endroit où l’OID a été terminé dans la pile, jusqu’au haut de la pile. Un appel Terminé peut inspecter ou modifier la requête OID, et inspecter ou modifier le code d’achèvement de l’OID status.

Le diagramme suivant illustre le cas classique, où un protocole émet une requête OID synchrone et où les filtres n’interceptent pas la requête.

Séquence d’appels de fonction pour les requêtes OID synchrones.

Notez que le modèle d’appel pour les OID synchrones est itératif. Cela maintient l’utilisation de la pile limitée par une constante, éliminant ainsi la nécessité de développer la pile.

Si un pilote de filtre intercepte un OID synchrone dans son gestionnaire de problèmes, l’OID n’est pas attribué aux filtres inférieurs ni au pilote de carte réseau. Toutefois, les gestionnaires complets pour les filtres plus élevés sont toujours appelés, comme illustré dans le diagramme suivant :

Séquence d’appels de fonction pour les requêtes OID synchrones avec une interception par un filtre.

Allocations de mémoire minimales

Les requêtes OID régulières et directes nécessitent un pilote de filtre pour cloner un NDIS_OID_REQUEST. En revanche, les requêtes OID synchrones ne sont pas autorisées à être clonées. L’avantage de cette conception est que les OID synchrones ont une latence plus faible ( la requête OID n’est pas clonées à plusieurs reprises car elle se déplace dans la pile de filtres) et il y a moins de possibilités d’échec.

Toutefois, cela soulève un nouveau problème. Si l’OID ne peut pas être cloné, où un pilote de filtre stocke-t-il son état par requête ? Par exemple, supposons qu’un pilote de filtre traduit un OID en un autre. Sur le chemin vers le bas de la pile, le filtre doit enregistrer l’ancien OID. Lors de la sauvegarde de la pile, le filtre doit restaurer l’ancien OID.

Pour résoudre ce problème, NDIS alloue un emplacement de taille pointeur pour chaque pilote de filtre, pour chaque requête OID synchrone en cours d’exécution. NDIS conserve cet emplacement entre l’appel du gestionnaire de problèmes d’un filtre et son gestionnaire Complete. Cela permet au gestionnaire de problèmes d’enregistrer l’état consommé ultérieurement par le gestionnaire Complete. L’extrait de code suivant affiche un exemple.

NDIS_STATUS
MyFilterSynchronousOidRequest(
  _In_ NDIS_HANDLE FilterModuleContext,
  _Inout_ NDIS_OID_REQUEST *OidRequest,
  _Outptr_result_maybenull_ PVOID *CallContext)
{
  if ( . . . should intercept this OID . . . )
  {
    // preserve the original buffer in the CallContext
    *CallContext = OidRequest->DATA.SET_INFORMATION.InformationBuffer;

    // replace the buffer with a new one
    OidRequest->DATA.SET_INFORMATION.InformationBuffer = . . . something . . .;
  }

  return NDIS_STATUS_SUCCESS;
}

VOID
MyFilterSynchronousOidRequestComplete(
  _In_ NDIS_HANDLE FilterModuleContext,
  _Inout_ NDIS_OID_REQUEST *OidRequest,
  _Inout_ NDIS_STATUS *Status,
  _In_ PVOID CallContext)
{
  // if the context is not null, we must have replaced the buffer.
  if (CallContext != null)
  {
    // Copy the data from the miniport back into the protocol’s original buffer.
    RtlCopyMemory(CallContext, OidRequest->DATA.SET_INFORMATION.InformationBuffer,...);
     
    // restore the original buffer into the OID request
    OidRequest->DATA.SET_INFORMATION.InformationBuffer = CallContext;
  }
}

NDIS enregistre un PVOID par filtre par appel. NDIS alloue de manière heuristique un nombre raisonnable d’emplacements sur la pile, de sorte qu’il n’y ait aucune allocation de pool dans le cas courant. Il ne s’agit généralement pas plus de sept filtres. Si l’utilisateur configure un cas pathologique, NDIS revient à une allocation de pool.

Réutilisable réduit

Prenons l’exemple réutilisable de l’exemple réutilisable pour la gestion des requêtes OID régulières ou directes. Ce code est le coût d’entrée uniquement pour inscrire un gestionnaire OID. Si vous souhaitez émettre vos propres OID, vous devez ajouter une douzaine de lignes réutilisables. Avec les OID synchrones, il n’est pas nécessaire d’ajouter de la complexité à la gestion de l’achèvement asynchrone. Par conséquent, vous pouvez découper une grande partie de ce réutilisable.

Voici un gestionnaire de problèmes minimal avec les OID synchrones :

NDIS_STATUS
MyFilterSynchronousOidRequest(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  PVOID *CallContext)
{
  return NDIS_STATUS_SUCCESS;
}

Si vous souhaitez intercepter ou modifier un OID particulier, vous pouvez le faire en ajoutant seulement quelques lignes de code. Le gestionnaire Complete minimal est encore plus simple :

VOID
MyFilterSynchronousOidRequestComplete(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  NDIS_STATUS *Status,
  PVOID CallContext)
{
  return;
}

De même, un pilote de filtre peut émettre sa propre requête OID synchrone à l’aide d’une seule ligne de code :

status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);

En revanche, un pilote de filtre qui doit émettre un OID normal ou direct doit configurer un gestionnaire de saisie semi-automatique asynchrone et implémenter du code pour distinguer ses propres complétions d’OID des achèvements des OID qu’il vient de cloner. Un exemple de ce réutilisable est illustré dans Exemple réutilisable pour l’émission d’une requête OID standard.

Interopérabilité

Bien que les styles d’appel Standard, Direct et Synchrone utilisent tous les mêmes structures de données, les pipelines n’utilisent pas le même gestionnaire dans le miniport. En outre, certains OID ne peuvent pas être utilisés dans certains pipelines. Par exemple, OID_PNP_SET_POWER nécessite une synchronisation minutieuse et force souvent le miniport à effectuer des appels bloquants. Cela rend sa gestion difficile dans un rappel d’OID direct et empêche son utilisation dans un rappel OID synchrone.

Par conséquent, tout comme avec les requêtes OID directes, les appels OID synchrones ne peuvent être utilisés qu’avec un sous-ensemble d’OID. Dans Windows 10 version 1709, seul l’OID OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES utilisé dans la mise à l’échelle côté réception version 2 (RSSv2) est pris en charge dans le chemin d’accès OID synchrone.

Implémentation de requêtes OID synchrones

Pour plus d’informations sur l’implémentation de l’interface de requête OID synchrone dans les pilotes, consultez les rubriques suivantes :