[Rede de Computadores] Visão geral da rede Kernel Linux

 Objetivo do artigo

  • Entenda a arquitetura de rede do kernel Linux
  • Adquira habilidades de gerenciamento de pacotes IP usando filtros de pacotes de rede ou firewalls
  • Familiarize-se com o uso de soquetes no nível do kernel Linux

Visão geral

        O desenvolvimento de aplicações de rede cresceu exponencialmente nos últimos anos, o que aumentou a velocidade e os requisitos de produção dos subsistemas de rede do sistema. O subsistema de rede não é um componente obrigatório do kernel Linux (o kernel Linux pode ser compilado sem suporte de rede). No entanto, muito poucos sistemas de computação (mesmo dispositivos incorporados) são difíceis de funcionar sem suporte de rede porque todos precisam estar conectados à Internet. Os sistemas operacionais modernos usam a pilha de protocolos TCP/IP, que implementa todas as camadas de protocolo abaixo da camada de transporte. Os protocolos da camada de aplicação geralmente são implementados no espaço do usuário (HTTP, FTP, SSH, etc.).

rede de espaço do usuário

        No espaço do usuário, a comunicação de rede é abstraída em soquetes. Os soquetes abstraem canais de comunicação e são baseados na interface interativa da pilha de protocolos TCP/IP do kernel. Um soquete IP está associado a um endereço IP, um protocolo da camada de transporte (TCP, UDP, etc.) e uma porta. Chamadas de função comuns usando soquetes incluem: criar (soquete), inicializar (vincular), conectar (conectar) e fechar o soquete (fechar).

        Os soquetes TCP completam a comunicação de rede por meio de chamadas de leitura/gravação ou recv/envio, enquanto o UDP usa chamadas de interface recvfrom/sento para concluir chamadas de rede. As operações de transmissão e recepção durante o processo de comunicação são transparentes para a aplicação, ou seja, o encapsulamento dos dados e a transmissão da rede são determinados pelo kernel. No entanto, também é possível implementar a pilha de protocolos TCP/IP no espaço do usuário através de soquetes brutos (usando a opção PF_PACKET ao ensinar soquetes) ou implementar protocolos da camada de aplicação no kernel (como o servidor web TUX).

        Para obter mais informações sobre programação de soquetes no espaço do usuário, consulte  o Guia de programação de rede do Beej. 

Comunicação de rede Linux

        O kernel do Linux fornece três estruturas de dados básicas para trabalho de pacotes de rede: struct socket, struct sock e struct sk_buff.

        Os dois primeiros são abstrações de soquete:

  • O soquete struct é muito semelhante à abstração do espaço do usuário que é o soquete BSD usado para escrever aplicativos de rede
  • struct sock ou soquete INET na terminologia Linux é a representação da camada de rede de um soquete.

        As duas estruturas estão relacionadas: struct socket contém os campos do soquete INET e struct sock possui um soquete BSD que os contém.

        A estrutura struct sk_buff é uma representação dos pacotes de rede e seu status. Esta estrutura é criada quando o kernel recebe um pacote de dados.O pacote de dados pode ser transmitido do espaço do usuário ou da interface de rede.

estrutura de soquete

        A estrutura struct socket é a representação de um soquete BSD no kernel, e as operações executadas nele são muito semelhantes àquelas fornecidas pelo kernel (por meio de chamadas de sistema). Algumas operações comuns de soquete (criação, inicialização/ligação, fechamento, etc.) resultam em chamadas de sistema específicas que usam a estrutura de soquete struct.

        A operação struct socket é implementada em net/socket.c e é independente do tipo de protocolo específico. Portanto, a estrutura do soquete struct é uma interface comum para várias implementações de operação de rede. Normalmente, essas operações começam com o prefixo sock_.

Operações em estruturas de soquete

criação

A operação de criação é semelhante a chamar a função socket() do espaço do usuário, mas o soquete criado é armazenado no parâmetro res:

  • int sock_create(int family, int typ, int protocol, struct socket **res) cria um soquete após a chamada do sistema socket();
  • Int sock_create_kern(struct net *net, int family, int type, int protocol, struct socket **ret) cria um soquete de kernel;
  • int sock_create_lite(int family, int type, int protocol, struct **res) cria um soquete de kernel sem verificação de integridade

Essas chamadas são as seguintes:

  • net é uma referência a um namespace de rede. Normalmente usamos init_net para inicializá-lo.
  • family representa a família de protocolos usada para transmissão de informações, começando com a string PF_ e carregando a família de protocolos. Esta constante representa a família de protocolos e é definida em linux/socket.h. TCP/IP geralmente usa PF_INET
  • type é o tipo de soquete, definido em linux/net.h. Geralmente a comunicação orientada a conexão usa SOCK_STREAM, enquanto a comunicação sem conexão usa SOCK_DGRAM.
  • protocolo indica o protocolo adotado, que está relacionado ao tipo e é definido em linux/in.h. TCP usa IPROTO_TCP e UDP usa IPROTO_UDP.

        Para criar um soquete TCP no espaço do kernel, você deve chamar:

struct socket *sock;
int err;

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

        Crie um soquete UDP:

struct socket *sock;
int err;

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

         Existem exemplos de uso relacionados na função de processamento de chamadas do sistema sys_socket() (manipulador):

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

fechando

        Feche a conexão (soquete orientado à conexão) e libere os recursos relacionados:

  • void sock_release(struct socket *sock) chama a função release do campo ops da estrutura do soquete:
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/receber mensagens

        Use as seguintes funções para enviar e receber mensagens:

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

        As funções de envio e recebimento de mensagens chamarão as funções sendmsg e recvmsg do campo socket ops.As funções com prefixo kernel_ são usadas pelo soquete no kernel.

        Os parâmetros são os seguintes:

  • msg é uma estrutura struct msghdr que contém as mensagens que precisam ser enviadas e recebidas. Os membros mais importantes desta estrutura são msg_name e msg_namelen, que para soquetes UDP devem ser preenchidos com o endereço de destino da mensagem enviada (struct sockaddr_in)
  • vec,uma estrutura struct kvec,contém um ponteiro para o buffer (contendo dados e tamanho).Esta estrutura é semelhante à estrutura struct iovec (a estrutura struct iovec é usada para o espaço do usuário, enquanto a estrutura struct kvec é usada para o espaço do kernel dados)

        Existem algumas rotinas de uso na função de processamento de chamadas do 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 do soquete 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;
};

        Os campos que precisam de explicação são:

  • ops - Esta estrutura armazena alguns ponteiros de função relacionados ao protocolo
  • sk - o soquete INET associado ao soquete

estrutura proto_ops estrutura

        A estrutura struct proto_ops contém a implementação de operações específicas (TCP/UDP, etc.).Essas funções serão chamadas por funções comuns de struct socket (sock_release(), sock_sendmsg(), etc.).

        A estrutura struct proto_ops também contém algumas dicas para estas implementações 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);
      //...
}

        A inicialização do campo ops do soquete struct é implementada através da função __sock_create(). Cada protocolo é especificado chamando a função create(). Uma chamada equivalente é a implementação da função __sock_create():

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

        Isso inicializa os ponteiros de função para o tipo de protocolo do soquete, e sock_register() e sock_unregister() são chamados para preencher o vetor net_fanilies.

        Para as demais operações de soquete (exceto criação, fechamento, envio e recebimento), elas também serão chamadas através de ponteiros, como a função 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 */
      }
//...

Acho que você gosta

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