[Réseau informatique] Présentation du réseau du noyau Linux

 Objectif de l'article

  • Comprendre l'architecture réseau du noyau Linux
  • Acquérir des compétences en gestion de paquets IP (paquets) à l'aide de filtres de paquets réseau ou de pare-feu
  • Être familier avec l'utilisation des sockets au niveau du noyau Linux

Aperçu

        Le développement d'applications réseau a connu une croissance exponentielle au cours des dernières années, ce qui a augmenté les exigences de vitesse et de production des sous-systèmes du réseau système. Le sous-système réseau n'est pas un composant obligatoire du noyau Linux (le noyau Linux peut être compilé sans support réseau). Cependant, très peu de systèmes informatiques (même les appareils embarqués) sont difficiles à réaliser sans support réseau car ils doivent tous être connectés à Internet. Les systèmes d'exploitation modernes utilisent la pile de protocoles TCP/IP, qui implémente toutes les couches de protocole situées sous la couche de transport. Les protocoles de la couche application sont généralement implémentés dans l'espace utilisateur (HTTP, FTP, SSH, etc.).

réseau d'espace utilisateur

        Dans l'espace utilisateur, la communication réseau est résumée en sockets. Les sockets résument les canaux de communication et sont basées sur l'interface interactive de la pile de protocoles TCP/IP du noyau. Un socket IP est associé à une adresse IP, un protocole de couche transport (TCP, UDP, etc.) et un port. Les appels de fonction courants utilisant des sockets incluent : créer (socket), initialiser (lier), connecter (connecter) et fermer le socket (fermer).

        Les sockets TCP complètent la communication réseau via des appels de lecture/écriture ou de réception/envoi, tandis que UDP utilise les appels d'interface recvfrom/sento pour terminer les appels réseau. Les opérations de transmission et de réception au cours du processus de communication sont transparentes pour l'application, c'est-à-dire que l'encapsulation des données et la transmission réseau sont déterminées par le noyau. Cependant, il est également possible d'implémenter la pile de protocoles TCP/IP dans l'espace utilisateur via des sockets bruts (en utilisant l'option PF_PACKET lors de l'apprentissage des sockets), ou d'implémenter des protocoles de couche application dans le noyau (comme le serveur Web TUX).

        Pour plus d'informations sur la programmation des sockets dans l'espace utilisateur, consultez  le Guide de programmation réseau de Beej. 

Communication réseau Linux

        Le noyau Linux fournit trois structures de données de base pour le travail sur les paquets réseau : struct socket, struct sock et struct sk_buff.

        Les deux premiers sont des abstractions de socket :

  • Le socket struct est très similaire à l'abstraction de l'espace utilisateur qui est le socket BSD utilisé pour écrire des applications réseau.
  • struct sock ou socket INET dans la terminologie Linux est la représentation de la couche réseau d'un socket.

        Les deux structures sont liées : struct socket contient les champs du socket INET et struct sock possède un socket BSD qui le contient.

        La structure struct sk_buff est une représentation des paquets réseau et de leur état. Cette structure est créée lorsque le noyau reçoit un paquet de données. Le paquet de données peut être transmis depuis l'espace utilisateur ou depuis l'interface réseau.

structure de socket struct

        La structure struct socket est la représentation d'un socket BSD dans le noyau, et les opérations effectuées dessus sont très similaires à celles fournies par le noyau (via des appels système). Certaines opérations courantes sur les sockets (création, initialisation/liaison, fermeture, etc.) entraînent des appels système spécifiques qui utilisent la structure struct socket.

        L'opération struct socket est implémentée dans net/socket.c et est indépendante du type de protocole spécifique. Par conséquent, la structure struct socket est une interface commune pour diverses implémentations d’opérations réseau. Généralement, ces opérations commencent par le préfixe sock_.

Opérations sur les structures de socket

création

L'opération de création est similaire à l'appel de la fonction socket() depuis l'espace utilisateur, mais la socket créée est stockée dans le paramètre res :

  • int sock_create(int family, int typ, int protocol, struct socket **res) crée un socket après l'appel système socket() ;
  • Int sock_create_kern(struct net *net, int family, int type, int protocol, struct socket **ret) crée un socket noyau ;
  • int sock_create_lite (int family, int type, int protocol, struct **res) crée un socket noyau sans contrôle d'intégrité

Ces appels sont les suivants :

  • net est une référence à un espace de noms réseau. Habituellement, nous utilisons init_net pour l'initialiser.
  • family représente la famille de protocoles utilisée pour la transmission des informations, en commençant par la chaîne PF_ et portant la famille de protocoles. Cette constante représente la famille de protocoles et est définie dans linux/socket.h. TCP/IP utilise généralement PF_INET
  • type est le type de socket, défini dans linux/net.h. Habituellement, la communication orientée connexion utilise SOCK_STREAM, tandis que la communication sans connexion utilise SOCK_DGRAM.
  • protocol indique le protocole adopté, qui est lié au type et est défini dans linux/in.h. TCP utilise IPROTO_TCP et UDP utilise IPROTO_UDP.

        Pour créer un socket TCP dans l'espace noyau, vous devez appeler :

struct socket *sock;
int err;

err = sock_create_kern(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
if (err < 0) {
        /* handle error */
}

        Créez un socket UDP :

struct socket *sock;
int err;

err = sock_create_kern(&init_net, PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock);
if (err < 0) {
        /* handle error */
}

         Il existe des exemples d'utilisation associés dans la fonction de traitement des appels système sys_socket() (gestionnaire) :

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
      int retval;
      struct socket *sock;
      int flags;

      /* Check the SOCK_* constants for consistency.  */
      BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
      BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
      BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
      BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

      flags = type & ~SOCK_TYPE_MASK;
      if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
              return -EINVAL;
      type &= SOCK_TYPE_MASK;

      if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
              flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

      retval = sock_create(family, type, protocol, &sock);
      if (retval < 0)
              goto out;

      return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

fermeture

        Fermez la connexion (socket orienté connexion) et libérez les ressources associées :

  • void sock_release(struct socket *sock) appelle la fonction release du champ ops de la structure socket :
void sock_release(struct socket *sock)
{
      if (sock->ops) {
              struct module *owner = sock->ops->owner;

              sock->ops->release(sock);
              sock->ops = NULL;
              module_put(owner);
      }
      //...
}

envoyer/recevoir des messages

        Utilisez les fonctions suivantes pour envoyer et recevoir des messages :

int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags);
int kernel_recvmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size, int flags);
int sock_sendmsg(struct socket *sock, struct msghdr *msg);
int kernel_sendmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size);

        Les fonctions d'envoi et de réception de messages appelleront les fonctions sendmsg et recvmsg du champ socket ops. Les fonctions avec le préfixe kernel_ sont utilisées par le socket dans le noyau.

        Les paramètres sont les suivants :

  • msg est une structure struct msghdr qui contient les messages qui doivent être envoyés et reçus. Les membres les plus importants de cette structure sont msg_name et msg_namelen, qui pour les sockets UDP doivent être renseignés avec l'adresse de destination du message envoyé (struct sockaddr_in).
  • vec, une structure struct kvec, contient un pointeur vers le tampon (contenant les données et la taille). Cette structure est similaire à la structure struct iovec (la structure struct iovec est utilisée pour l'espace utilisateur, tandis que la structure struct kvec est utilisée pour l'espace noyau données)

        Il existe quelques routines d'utilisation dans la fonction de traitement des appels système sys_sendto() :

SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
              unsigned int, flags, struct sockaddr __user *, addr,
              int, addr_len)
{
      struct socket *sock;
      struct sockaddr_storage address;
      int err;
      struct msghdr msg;
      struct iovec iov;
      int fput_needed;

      err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);
      if (unlikely(err))
              return err;
      sock = sockfd_lookup_light(fd, &err, &fput_needed);
      if (!sock)
              goto out;

      msg.msg_name = NULL;
      msg.msg_control = NULL;
      msg.msg_controllen = 0;
      msg.msg_namelen = 0;
      if (addr) {
              err = move_addr_to_kernel(addr, addr_len, &address);
              if (err < 0)
                      goto out_put;
              msg.msg_name = (struct sockaddr *)&address;
              msg.msg_namelen = addr_len;
      }
      if (sock->file->f_flags & O_NONBLOCK)
              flags |= MSG_DONTWAIT;
      msg.msg_flags = flags;
      err = sock_sendmsg(sock, &msg);

out_put:
      fput_light(sock->file, fput_needed);
out:
      return err;
}

        Champs du socket struct :

/**
 *  struct socket - general BSD socket
 *  @state: socket state (%SS_CONNECTED, etc)
 *  @type: socket type (%SOCK_STREAM, etc)
 *  @flags: socket flags (%SOCK_NOSPACE, etc)
 *  @ops: protocol specific socket operations
 *  @file: File back pointer for gc
 *  @sk: internal networking protocol agnostic socket representation
 *  @wq: wait queue for several uses
 */
struct socket {
      socket_state            state;

      short                   type;

      unsigned long           flags;

      struct socket_wq __rcu  *wq;

      struct file             *file;
      struct sock             *sk;
      const struct proto_ops  *ops;
};

        Les champs qui nécessitent des explications sont :

  • ops - Cette structure stocke certains pointeurs de fonction liés au protocole
  • sk - le socket INET associé au socket

structurer la structure proto_ops

        La structure struct proto_ops contient l'implémentation d'opérations spécifiques (TCP/UDP, etc.). Ces fonctions seront appelées par les fonctions ordinaires de struct socket (sock_release(), sock_sendmsg(), etc.).

        La structure struct proto_ops contient également quelques pointeurs vers ces implémentations de protocole :

struct proto_ops {
      int             family;
      struct module   *owner;
      int             (*release)   (struct socket *sock);
      int             (*bind)      (struct socket *sock,
                                    struct sockaddr *myaddr,
                                    int sockaddr_len);
      int             (*connect)   (struct socket *sock,
                                    struct sockaddr *vaddr,
                                    int sockaddr_len, int flags);
      int             (*socketpair)(struct socket *sock1,
                                    struct socket *sock2);
      int             (*accept)    (struct socket *sock,
                                    struct socket *newsock, int flags, bool kern);
      int             (*getname)   (struct socket *sock,
                                    struct sockaddr *addr,
                                    int peer);
      //...
}

        L'initialisation du champ ops du socket struct est implémentée via la fonction __sock_create(). Chaque protocole est spécifié en appelant la fonction create(). Un appel équivalent est l'implémentation de la fonction __sock_create() :

//...
      err = pf->create(net, sock, protocol, kern);
      if (err < 0)
              goto out_module_put;
//...

        Cela initialise les pointeurs de fonction vers le type de protocole du socket, et sock_register() et sock_unregister() sont appelés pour remplir le vecteur net_fanilies.

        Pour les opérations de socket restantes (sauf création, fermeture, envoi et réception), elles seront également appelées via des pointeurs, comme la fonction bind :

#define MY_PORT 60000

struct sockaddr_in addr = {
      .sin_family = AF_INET,
      .sin_port = htons (MY_PORT),
      .sin_addr = { htonl (INADDR_LOOPBACK) }
};

//...
      err = sock->ops->bind (sock, (struct sockaddr *) &addr, sizeof(addr));
      if (err < 0) {
              /* handle error */
      }
//...

Je suppose que tu aimes

Origine blog.csdn.net/BillyThe/article/details/133430388
conseillé
Classement