[Red informática] Descripción general de la red del kernel de Linux

 Propósito del artículo

  • Comprender la arquitectura de red del kernel de Linux
  • Adquiera habilidades de administración de paquetes (paquetes) IP utilizando filtros de paquetes de red o firewalls
  • Familiarizarse con el uso de sockets a nivel del kernel de Linux.

Descripción general

        El desarrollo de aplicaciones de red ha crecido exponencialmente en los últimos años, lo que ha aumentado la velocidad y los requisitos de producción de los subsistemas de red del sistema. El subsistema de red no es un componente requerido del kernel de Linux (el kernel de Linux se puede compilar sin soporte de red). Sin embargo, muy pocos sistemas informáticos (incluso los dispositivos integrados) son difíciles de prescindir del soporte de red porque todos necesitan estar conectados a Internet. Los sistemas operativos modernos utilizan la pila de protocolos TCP/IP, que implementa todas las capas de protocolo debajo de la capa de transporte. Los protocolos de la capa de aplicación generalmente se implementan en el espacio del usuario (HTTP, FTP, SSH, etc.).

red de espacio de usuario

        En el espacio del usuario, la comunicación de red se abstrae en sockets, que abstraen canales de comunicación y se basan en la interfaz interactiva de la pila de protocolos TCP/IP del kernel. Un socket IP está asociado con una dirección IP, un protocolo de capa de transporte (TCP, UDP, etc.) y un puerto. Las llamadas a funciones comunes que utilizan sockets incluyen: crear (socket), inicializar (vincular), conectar (conectar) y cerrar el socket (cerrar).

        Los sockets TCP completan la comunicación de red mediante llamadas de lectura/escritura o recepción/envío, mientras que UDP utiliza llamadas de interfaz recvfrom/sento para completar las llamadas de red. Las operaciones de transmisión y recepción durante el proceso de comunicación son transparentes para la aplicación, es decir, la encapsulación de datos y la transmisión de red están determinadas por el núcleo. Sin embargo, también es posible implementar la pila de protocolos TCP/IP en el espacio del usuario a través de sockets sin formato (usando la opción PF_PACKET al enseñar sockets), o implementar protocolos de capa de aplicación en el kernel (como el servidor web TUX).

        Para obtener más información sobre la programación de sockets en el espacio del usuario, consulte  la Guía de programación de redes de Beej. 

comunicación de red linux

        El kernel de Linux proporciona tres estructuras de datos básicas para el trabajo de paquetes de red: struct socket, struct sock y struct sk_buff.

        Los dos primeros son abstracciones de socket:

  • El socket de estructura es muy similar a la abstracción del espacio de usuario, que es el socket BSD utilizado para escribir aplicaciones de red.
  • struct sock o INET socket en la terminología de Linux es la representación de la capa de red de un socket.

        Las dos estructuras están relacionadas: struct socket contiene los campos del socket INET y struct sock tiene un socket BSD que los contiene.

        La estructura struct sk_buff es una representación de los paquetes de red y su estado. Esta estructura se crea cuando el kernel recibe un paquete de datos, que puede transmitirse desde el espacio del usuario o desde la interfaz de red.

estructura del zócalo

        La estructura struct socket es la representación de un socket BSD en el kernel, y las operaciones realizadas en él son muy similares a las proporcionadas por el kernel (a través de llamadas al sistema). Algunas operaciones de socket comunes (creación, inicialización/vinculación, cierre, etc.) dan como resultado llamadas al sistema específicas que utilizan la estructura de socket de estructura.

        La operación de estructura de socket se implementa en net/socket.c y es independiente del tipo de protocolo específico. Por lo tanto, la estructura de socket de estructura es una interfaz común para varias implementaciones de operaciones de red. Normalmente, estas operaciones comienzan con el prefijo sock_.

Operaciones en estructuras de encaje.

creación

La operación de creación es similar a llamar a la función socket() desde el espacio de usuario, pero el socket creado se almacena en el parámetro res:

  • int sock_create(int family, int typ, int protocol, struct socket **res) crea un socket después de la llamada al sistema socket();
  • Int sock_create_kern(struct net *net, int family, int type, int protocolo, struct socket **ret) crea un socket del kernel;
  • int sock_create_lite(int family, int type, int protocol, struct **res) crea un socket de kernel sin verificación de cordura

Estas convocatorias son las siguientes:

  • net es una referencia a un espacio de nombres de red. Generalmente usamos init_net para inicializarlo.
  • familia representa la familia de protocolos utilizada para la transmisión de información, comenzando con la cadena PF_ y llevando la familia de protocolos. Esta constante representa la familia de protocolos y está definida en linux/socket.h. TCP/IP generalmente usa PF_INET
  • tipo es el tipo de socket, definido en linux/net.h. Por lo general, la comunicación orientada a la conexión usa SOCK_STREAM, mientras que la comunicación sin conexión usa SOCK_DGRAM.
  • protocolo indica el protocolo adoptado, que está relacionado con el tipo y está definido en linux/in.h. TCP usa IPROTO_TCP y UDP usa IPROTO_UDP.

        Para crear un socket TCP en el espacio del kernel, debe llamar:

struct socket *sock;
int err;

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

        Cree 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 */
}

         Hay ejemplos de uso relacionados en la función de procesamiento de llamadas del sistema sys_socket() (controlador):

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));
}

clausura

        Cierre la conexión (socket orientado a la conexión) y libere los recursos relacionados:

  • void sock_release(struct socket *sock) llama a la función de liberación del campo de operaciones de la estructura del 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);
      }
      //...
}

enviar/recibir mensajes

        Utilice las siguientes funciones para enviar y recibir mensajes:

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);

        Las funciones de envío y recepción de mensajes llamarán a las funciones sendmsg y recvmsg del campo de operaciones de socket. Las funciones con el prefijo kernel_ son utilizadas por el socket en el kernel.

        Los parámetros son los siguientes:

  • msg es una estructura struct msghdr que contiene los mensajes que deben enviarse y recibirse. Los miembros más importantes de esta estructura son msg_name y msg_namelen, que para sockets UDP deben completarse con la dirección de destino del mensaje enviado (struct sockaddr_in)
  • vec, una estructura struct kvec, contiene un puntero al búfer (que contiene datos y tamaño). Esta estructura es similar a la estructura struct iovec (la estructura struct iovec se usa para el espacio del usuario, mientras que la estructura struct kvec se usa para el espacio del kernel). datos)

        Hay algunas rutinas de uso en la función de procesamiento de llamadas del sistema 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;
}

        Campos de estructura socket:

/**
 *  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;
};

        Los campos que necesitan explicación son:

  • ops: esta estructura almacena algunos punteros de función relacionados con el protocolo.
  • sk: el socket INET asociado con el socket

estructura proto_ops estructura

        La estructura struct proto_ops contiene la implementación de operaciones específicas (TCP/UDP, etc.) Estas funciones serán llamadas por funciones ordinarias de struct socket (sock_release(), sock_sendmsg(), etc.).

        La estructura struct proto_ops también contiene algunos punteros a estas implementaciones de protocolo:

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);
      //...
}

        La inicialización del campo de operaciones de struct socket se implementa a través de la función __sock_create(). Cada protocolo se especifica llamando a la función create(). Una llamada equivalente es la implementación de la función __sock_create():

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

        Esto inicializa los punteros de función al tipo de protocolo del socket, y se llama a sock_register() y sock_unregister() para completar el vector net_fanilies.

        Para las operaciones de socket restantes (excepto creación, cierre, envío y recepción), también se llamarán mediante punteros, como la función de vinculación:

#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 */
      }
//...

Supongo que te gusta

Origin blog.csdn.net/BillyThe/article/details/133430388
Recomendado
Clasificación