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:
- Abre a interface de rede (por padrão,
"eth0"
). - Obtém o endereço MAC da interface.
- Envia periodicamente um BPDU com informações da Bridge.
- Captura BPDUs recebidos.
- Compara os BPDUs e decide se a Raspberry Pi é a Root Bridge.
- Atualiza o estado da porta baseado nas flags do BPDU.
🚀 Como compilar e rodar o programa
- Instale a biblioteca de desenvolvimento do
libpcap
:
sudo apt-get install libpcap-dev
- Compile o código:
g++ rstp.cpp -o rstp -lpcap
- Execute com permissões de root:
sudo ./rstp
🔍 Visualizando no Wireshark
- Abra o Wireshark.
- Selecione a interface Ethernet conectada à Raspberry Pi.
- 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.
🔎 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.