FC

Filipe Cardoso07/04/2025 11:36
Compartilhe

Rapid Spanning Tree Protocol (RSTP) com Raspberry Pi usando libpcap

  • #Linux
  • #C++

📌 O que é RSTP?

O Rapid Spanning Tree Protocol (RSTP) é uma evolução do STP (Spanning Tree Protocol) definido na norma IEEE 802.1w. Ele tem como objetivo eliminar laços em redes comutadas (switches), garantindo uma topologia sem loops e promovendo recuperação rápida de caminhos quando há falhas de conexão.

🌳 O que é a Root Bridge?

A Root Bridge é o switch principal na rede STP/RSTP. Toda a topologia é organizada em torno dele. Ela é eleita com base na Bridge ID, que é formada por:

  • Prioridade (2 bytes)
  • MAC address (6 bytes)

💡 O switch com o menor Bridge ID é eleito como Root Bridge. Ou seja, a menor prioridade vence; em caso de empate, o menor MAC address decide.

⚙️ Como ocorre a eleição da Root Bridge?

Cada switch envia mensagens chamadas BPDU (Bridge Protocol Data Unit) para anunciar sua presença. Essas mensagens contêm:

  • ID da root bridge que o switch acredita ser a root
  • Custo do caminho até ela
  • ID do próprio switch

Com base nesses BPDUs, os switches comparam os dados e chegam a um consenso sobre qual dispositivo será a Root Bridge.

📶 Estados das portas no RSTP

O RSTP possui 3 estados principais de porta:

EstadoDescriçãoDiscardingPorta não encaminha frames nem aprende MACs. Usada para eliminar loops.LearningPorta aprende MACs, mas ainda não encaminha frames.ForwardingPorta encaminha frames e aprende MACs normalmente.

RSTP é mais rápido que o STP tradicional, pois reduz o tempo de convergência da rede (ou seja, tempo para reorganizar a topologia).

🧠 Estrutura de um BPDU (Bridge Protocol Data Unit)

No código exemplo, usamos uma estrutura BPDU para simular o envio de mensagens RSTP.

#include <iostream>
#include <pcap.h>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netinet/ether.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>


#define BPDU_TYPE_CONFIG 0x00
#define BPDU_TYPE_TCN 0x80


struct BPDU {
  uint8_t protocol_id[2];
  uint8_t version;
  uint8_t bpdu_type;
  uint8_t flags;
  uint8_t root_id[8];
  uint32_t root_path_cost;
  uint8_t bridge_id[8];
  uint16_t port_id;
  uint16_t message_age;
  uint16_t max_age;
  uint16_t hello_time;
  uint16_t forward_delay;
};


void getMACAddress(const char* iface, uint8_t* mac) {
  int fd = socket(AF_INET, SOCK_DGRAM, 0);
  struct ifreq ifr;
  strcpy(ifr.ifr_name, iface);
  ioctl(fd, SIOCGIFHWADDR, &ifr);
  close(fd);
  memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
}
void sendBPDU(pcap_t *handle, BPDU &bpdu, const uint8_t* src_mac) {
  uint8_t frame[1500];
  memset(frame, 0, sizeof(frame));


  // Destino: endereço multicast STP
  uint8_t dst_mac[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00};


  // Montar cabeçalho Ethernet
  memcpy(frame, dst_mac, 6);
  memcpy(frame + 6, src_mac, 6);
  frame[12] = 0x00;
  frame[13] = 0x27;  // Tipo para STP (em STP tradicional é encapsulado via LLC, isso é simplificado)


  // Inserir BPDU no payload
  memcpy(frame + 14, &bpdu, sizeof(bpdu));


  int frame_len = 14 + sizeof(bpdu);


  if (pcap_sendpacket(handle, frame, frame_len) != 0) {
      std::cerr << "Erro ao enviar o BPDU: " << pcap_geterr(handle) << std::endl;
  } else {
      std::cout << "BPDU enviado com sucesso!" << std::endl;
  }
}


void processBPDU(const u_char *packet, BPDU &bpdu) {
  memcpy(&bpdu, packet, sizeof(bpdu));
  std::cout << "Processando BPDU recebido..." << std::endl;
  // Lógica para processar BPDU recebido e atualizar estado da rede
  // Exemplo: Atualizar root_id e root_path_cost se BPDU recebido for melhor
  if (memcmp(bpdu.root_id, packet + 8, 8) > 0) {
      memcpy(bpdu.root_id, packet + 8, 8);
      bpdu.root_path_cost = ntohl(*(uint32_t *)(packet + 16));
  }
}
void receiveBPDU(pcap_t *handle, BPDU &bpdu) {
  struct pcap_pkthdr header;
  const u_char *packet = pcap_next(handle, &header);


  if (packet != nullptr) {
      std::cout << "BPDU recebido!" << std::endl;
      processBPDU(packet, bpdu);
  }
}


bool isRootBridge(const BPDU &bpdu, const uint8_t* mac) {
  // Comparar prioridade e MAC address para determinar se é root bridge
  return memcmp(bpdu.root_id + 2, mac, 6) > 0;
}


void updatePortState(BPDU &bpdu) {
  // Lógica para atualizar estados das portas (discarding, learning, forwarding)
  std::cout << "Atualizando estados das portas..." << std::endl;
  // Exemplo: Atualizar estado da porta com base em BPDU recebido
  if (bpdu.flags & 0x01) {
      std::cout << "Porta em estado de forwarding." << std::endl;
  } else {
      std::cout << "Porta em estado de discarding." << std::endl;
  }
}
int main() {
  char errbuf[PCAP_ERRBUF_SIZE];
  pcap_t *handle;


  // Abrindo a interface de rede
  handle = pcap_open_live("eth0", BUFSIZ, 1, 1000, errbuf);
  if (handle == nullptr) {
      std::cerr << "Não foi possível abrir a interface eth0: " << errbuf << std::endl;
      return 1;
  }


  BPDU bpdu;
  memset(&bpdu, 0, sizeof(bpdu));
  bpdu.protocol_id[0] = 0x00;
  bpdu.protocol_id[1] = 0x00;
  bpdu.version = 0x02;
  bpdu.bpdu_type = BPDU_TYPE_CONFIG;
  // Configurar outros campos do BPDU conforme necessário


  uint8_t mac[6];
  getMACAddress("eth0", mac);
  memcpy(bpdu.bridge_id + 2, mac, 6);


  while (true) {
      sendBPDU(handle, bpdu, mac);
      receiveBPDU(handle, bpdu);
      if (isRootBridge(bpdu, mac)) {
          std::cout << "Raspberry Pi é a root bridge!" << std::endl;
      }
      updatePortState(bpdu);
      sleep(2); // Enviar e receber BPDUs a cada 2 segundos
  }


  // Fechando a interface
  pcap_close(handle);
  return 0;
}

💻 Código de exemplo em C++

✅ O que o programa faz?

Este código:

  1. Abre a interface de rede (por padrão, "eth0").
  2. Obtém o endereço MAC da interface.
  3. Envia periodicamente um BPDU com informações da Bridge.
  4. Captura BPDUs recebidos.
  5. Compara os BPDUs e decide se a Raspberry Pi é a Root Bridge.
  6. Atualiza o estado da porta baseado nas flags do BPDU.

🚀 Como compilar e rodar o programa

  1. Instale a biblioteca de desenvolvimento do libpcap:
sudo apt-get install libpcap-dev
  1. Compile o código:
g++ rstp.cpp -o rstp -lpcap
  1. Execute com permissões de root:
sudo ./rstp

🔍 Visualizando no Wireshark

  1. Abra o Wireshark.
  2. Selecione a interface Ethernet conectada à Raspberry Pi.
  3. Use o seguinte filtro de captura:
eth.dst == 01:80:c2:00:00:00
Este filtro captura somente pacotes multicast destinados ao endereço STP padrão.

image

🔎 Explicando partes importantes do código

1. Obter MAC address da interface

void getMACAddress(const char* iface, uint8_t* mac)

Usa ioctl() para pegar o MAC address de eth0.

2. Enviar BPDU

void sendBPDU(pcap_t *handle, BPDU &bpdu, const uint8_t* src_mac)
  • Monta o quadro Ethernet com:
  • Destino: 01:80:c2:00:00:00 (multicast STP)
  • Origem: MAC da interface
  • Tipo: 0x0027 (exemplo genérico)
  • Copia o conteúdo do BPDU como payload e envia usando pcap_sendpacket.

3. Receber BPDU

void receiveBPDU(pcap_t *handle, BPDU &bpdu)
  • Usa pcap_next() para capturar pacotes.
  • Se encontrar um pacote, chama processBPDU() para analisar.

4. Processar BPDU

void processBPDU(const u_char *packet, BPDU &bpdu)
  • Compara o root_id recebido com o atual.
  • Se o recebido for "melhor", atualiza os dados internos.

5. Verificar se é a Root Bridge

bool isRootBridge(const BPDU &bpdu, const uint8_t* mac)
  • Compara o MAC da Pi com o root_id para saber se ela é a root.

6. Atualizar estado da porta

void updatePortState(BPDU &bpdu)
  • Simula a atualização de estado de porta com base na flag do BPDU.

🧪 Dicas de testes

  • Use dois dispositivos com esse programa (ex: 2 Raspberry Pis ou um PC com Ethernet).
  • Ligue-os via um switch comum.
  • Use o Wireshark para observar os BPDUs trafegando.
  • Altere as prioridades e veja qual se torna a Root Bridge.

🧠 Conclusão

O RSTP é um protocolo essencial para garantir redes Ethernet livres de loops, mantendo a redundância e a estabilidade. Com este exemplo prático, você pode simular o comportamento de switches, entender como os pacotes são construídos e transmitidos, e visualizar isso em tempo real com o Wireshark.

Compartilhe
Comentários (1)
DIO Community
DIO Community - 07/04/2025 14:53

Filipe, esse artigo está muito bem explicado! O conteúdo sobre o Rapid Spanning Tree Protocol (RSTP) e como ele pode ser implementado com o uso de libpcap é bastante detalhado, o que ajuda a entender tanto o funcionamento do protocolo quanto a aplicação prática em redes. A analogia com a Root Bridge e a explicação sobre os diferentes estados das portas no RSTP são fundamentais para quem quer compreender como os switches se comportam em uma rede.

Os exemplos de código também são muito úteis, principalmente a parte de envio e recebimento de BPDUs com libpcap. A utilização do Wireshark para visualizar os pacotes no contexto do RSTP foi uma ótima adição.

Quais desafios você encontrou ao implementar o RSTP no Raspberry Pi e como as ferramentas que você utilizou facilitaram o processo?