Enlace de Dados
Video da aula estara disponivel em breve
A camada de enlace
A camada de enlace é responsável pela transferência de dados entre nós adjacentes (diretamente conectados). Enquanto a camada de rede (IP) lida com roteamento entre redes diferentes, a camada de enlace lida com a comunicação dentro de um único segmento de rede.
Suas responsabilidades principais são: enquadramento (framing) — delimitar onde um quadro começa e termina; controle de acesso ao meio (MAC) — decidir quem pode transmitir quando; detecção de erros — verificar se o quadro chegou íntegro; e endereçamento local — identificar dispositivos no mesmo segmento.
Ethernet e quadros (frames)
Quadro Ethernet:
+-----------+----------+------+---------+---------+-----+
| Preambulo | SFD | Dest | Source | Type | ... |
| 7 bytes | 1 byte | MAC | MAC | 2 bytes | |
| | |6 bytes|6 bytes | | |
+-----------+----------+------+---------+---------+-----+
|
+---------------------------------------------------+---+----+
| Payload (46-1500 bytes) | FCS|
| (pacote IP encapsulado) |4 B |
+--------------------------------------------------------+----+
MAC address: 48 bits, notacao hexadecimal
Exemplo: AA:BB:CC:DD:EE:FF
Primeiros 3 octetos: OUI (fabricante)
Ultimos 3 octetos: ID unico do dispositivo
FCS (Frame Check Sequence): CRC-32 para detecção de erros
Tamanho total do quadro Ethernet:
Mínimo: 64 bytes (46 payload + 18 overhead)
Máximo: 1518 bytes (1500 payload + 18 overhead)
Jumbo frames: até 9000 bytes de payload (datacenters)
Detecção de erros: CRC-32
O campo FCS (Frame Check Sequence) usa CRC-32 (Cyclic Redundancy Check) para detectar erros de transmissão. O transmissor calcula o CRC sobre todo o quadro e anexa o resultado. O receptor recalcula e compara — se diferente, o quadro é descartado silenciosamente.
import binascii
# Simulação de CRC-32 na camada de enlace
def compute_fcs(frame_bytes):
"""Calcula o FCS (CRC-32) de um quadro Ethernet."""
crc = binascii.crc32(frame_bytes) & 0xFFFFFFFF
return crc
def verify_frame(frame_bytes, expected_fcs):
"""Verifica integridade do quadro."""
computed = compute_fcs(frame_bytes)
if computed == expected_fcs:
print("Quadro íntegro — CRC OK")
return True
else:
print(f"ERRO: CRC esperado {expected_fcs:#x}, calculado {computed:#x}")
print("Quadro descartado (bit flip durante transmissão)")
return False
# Simulando envio e recebimento
dados = b"Hello Ethernet"
fcs = compute_fcs(dados)
print(f"FCS calculado: {fcs:#010x}")
verify_frame(dados, fcs) # OK
verify_frame(dados + b"X", fcs) # ERRO — dados corrompidos
MAC addresses
MAC (Media Access Control) addresses sao enderecos fisicos de 48 bits, atribuidos pelo fabricante e (em teoria) unicos globalmente. Diferente de enderecos IP (logicos, mutaveis), MACs sao fisicos e geralmente fixos.
import uuid
import subprocess
# Obter MAC address do host
mac = ':'.join([f'{(uuid.getnode() >> i) & 0xff:02x}'
for i in range(0, 48, 8)][::-1])
print(f"MAC address: {mac}")
# Ver tabela ARP (mapeamento IP -> MAC)
result = subprocess.run(["arp", "-a"], capture_output=True, text=True)
print(result.stdout)
Controle de acesso ao meio (MAC)
Quando múltiplos dispositivos compartilham o mesmo meio físico (como um segmento de rede sem fio ou um hub Ethernet antigo), é necessário um protocolo para evitar colisões — duas transmissões simultâneas que se corrompem mutuamente.
Protocolos de acesso ao meio:
1. CSMA/CD (Carrier Sense Multiple Access / Collision Detection)
Usado pelo Ethernet clássico (half-duplex)
Algoritmo:
1. Escuta o meio (carrier sense)
2. Se livre → transmite
3. Se ocupado → espera
4. Se colisão detectada durante transmissão:
a. Para de transmitir
b. Envia jam signal (32 bits)
c. Espera tempo aleatório (exponential backoff)
d. Tenta novamente
Exponential backoff:
Após n colisões, espera K × 512 bit-times
onde K é aleatório em [0, 2^n - 1]
Máximo: n = 10 → K em [0, 1023]
Após 16 colisões: desiste (frame descartado)
2. CSMA/CA (Collision Avoidance)
Usado pelo Wi-Fi (802.11) — não detecta colisões no ar
Algoritmo:
1. Escuta o meio
2. Se livre por DIFS → transmite
3. Receptor envia ACK
4. Se ACK não recebido → colisão, retransmite
RTS/CTS (opcional): resolve "hidden terminal problem"
A ---RTS---> AP ---CTS---> todos
(todos sabem que A vai transmitir → esperam)
Switches vs. Hubs
Hub (camada 1 - fisica):
Recebe quadro em uma porta -> replica para TODAS as outras portas
Nao olha MAC addresses. Colisoes frequentes. Obsoleto.
A --[frame]--> [HUB] --[frame]--> B
|---[frame]--> C (C recebe mesmo sem ser destino)
|---[frame]--> D (D tambem recebe)
Switch (camada 2 - enlace):
Mantém tabela MAC (MAC -> porta). Encaminha apenas para a porta correta.
Sem colisoes. Performance muito melhor.
A --[frame]--> [SWITCH] --[frame]--> B (apenas B recebe)
|
Tabela MAC:
AA:BB:CC:11 -> porta 1 (A)
AA:BB:CC:22 -> porta 2 (B)
AA:BB:CC:33 -> porta 3 (C)
ARP: Address Resolution Protocol
ARP resolve enderecos IP em enderecos MAC. Quando um host quer enviar um pacote para outro host na mesma rede, ele precisa saber o MAC address do destino.
Host A quer enviar para 192.168.1.5, mas so sabe o IP. Precisa do MAC.
1. A envia ARP Request (broadcast):
"Quem tem o IP 192.168.1.5? Me diga seu MAC."
Destino: FF:FF:FF:FF:FF:FF (broadcast - todos recebem)
2. Host B (192.168.1.5) responde com ARP Reply:
"Eu sou 192.168.1.5. Meu MAC e AA:BB:CC:DD:EE:55"
3. A guarda na ARP cache: 192.168.1.5 -> AA:BB:CC:DD:EE:55
4. A encapsula o pacote IP em um frame Ethernet com:
MAC destino: AA:BB:CC:DD:EE:55
MAC origem: (MAC do A)
Tipo: 0x0800 (IPv4)
Payload: pacote IP
Aprendizado da tabela MAC
Switches constroem sua tabela MAC automaticamente por um processo chamado self-learning: quando um quadro chega em uma porta, o switch registra o MAC de origem e a porta. Se o MAC de destino é desconhecido, o switch faz flooding — envia para todas as portas exceto a de origem.
import time
class EthernetSwitch:
"""Simulação de um switch Ethernet com self-learning."""
def __init__(self, num_ports, aging_time=300):
self.mac_table = {} # MAC → (porta, timestamp)
self.num_ports = num_ports
self.aging_time = aging_time
def receive_frame(self, src_mac, dst_mac, in_port):
"""Processa um quadro recebido."""
# Self-learning: registra MAC de origem
self.mac_table[src_mac] = (in_port, time.time())
# Forwarding: decide para onde enviar
if dst_mac == "FF:FF:FF:FF:FF:FF":
# Broadcast: envia para todas as portas exceto origem
out_ports = [p for p in range(self.num_ports) if p != in_port]
print(f" BROADCAST → portas {out_ports}")
elif dst_mac in self.mac_table:
# Unicast conhecido: envia para porta específica
out_port = self.mac_table[dst_mac][0]
print(f" FORWARD → porta {out_port}")
else:
# MAC desconhecido: flooding
out_ports = [p for p in range(self.num_ports) if p != in_port]
print(f" FLOOD (MAC desconhecido) → portas {out_ports}")
sw = EthernetSwitch(4)
sw.receive_frame("AA:11", "BB:22", 0) # Flood (BB:22 desconhecido)
sw.receive_frame("BB:22", "AA:11", 1) # Forward porta 0 (AA:11 aprendido)
VLANs
VLANs (Virtual LANs) permitem segmentar uma rede física em múltiplas redes lógicas isoladas. Hosts em VLANs diferentes não podem se comunicar diretamente — precisam de um roteador (inter-VLAN routing).
Sem VLAN: Com VLANs:
Todos no mesmo dominio VLAN 10 (Eng): A, B
de broadcast VLAN 20 (RH): C, D
A, B, C, D <-> broadcast A <-> B (VLAN 10)
(todos veem tudo) C <-> D (VLAN 20)
A -/-> C (bloqueado, VLANs diferentes)
802.1Q: adiciona tag de 4 bytes ao frame Ethernet
indicando a qual VLAN o frame pertence
Tag 802.1Q inserida no quadro Ethernet:
┌──────┬──────┬──────┬──────────┬──────┬─────────┬─────┐
│ Dst │ Src │ TPID │ PCP│DEI│ │ VID │ Type │ ... │
│ MAC │ MAC │0x8100│ 3b │1b │ │ 12b │ │ │
└──────┴──────┴──────┴──────────┴──────┴─────────┴─────┘
TPID: Tag Protocol ID (0x8100 = 802.1Q)
PCP: Priority Code Point (QoS)
VID: VLAN ID (0-4095, 12 bits → 4096 VLANs possíveis)
Trunk port vs Access port:
Access port: pertence a UMA VLAN, sem tag
Trunk port: carrega múltiplas VLANs, com tag 802.1Q
No harness.os
A camada de enlace parece distante do trabalho com agentes AI, mas os conceitos aparecem diretamente ao debugar cloud networking. Além disso, o self-learning do switch é um padrão que se repete no harness.os:
Trace completo: MCP server -> Neon Postgres
Camada 7 (Aplicacao): MCP tool call (JSON-RPC)
Camada 7 (Aplicacao): PostgreSQL wire protocol (query SQL)
Camada 4 (Transporte): TCP segmento (porta 5432)
Camada 3 (Rede): IP pacote (IP do Fly.io -> IP do Neon)
Camada 2 (Enlace): Ethernet frame (MAC do gateway Fly.io)
Camada 1 (Fisica): Sinais eletricos/opticos no datacenter
Quando "a conexao com o banco falha", o problema pode estar
em QUALQUER uma dessas camadas:
- DNS nao resolve (camada 7)
- Firewall bloqueia porta 5432 (camada 3/4)
- Rede do datacenter com falha (camada 2/1)
- TLS certificate expired (entre camada 4 e 7)
Entender todas as camadas = debugar qualquer problema de rede.
Resumo
- Camada de enlace: comunicação entre nós adjacentes — enquadramento, controle de acesso, detecção de erros
- Ethernet: protocolo dominante, quadros com MAC src/dst + payload + FCS (CRC-32)
- MAC addresses: 48 bits, físicos, atribuídos pelo fabricante (OUI + ID)
- CSMA/CD (Ethernet) e CSMA/CA (Wi-Fi): protocolos de controle de acesso ao meio
- Switch vs. Hub: switches usam tabela MAC com self-learning para encaminhamento seletivo
- ARP: resolve IP para MAC na mesma rede via broadcast + reply
- VLANs (802.1Q): segmentação lógica de redes físicas com tags de 12 bits
Homework
Exercício 1: Trace um pacote do MCP server (Fly.io) ao Neon Postgres por todas as camadas de rede.
- Comece na aplicação: qual é a mensagem MCP? Qual query SQL?
- Desça: TCP (porta?), IP (IPs?), Ethernet (MACs?), físico (fibra/elétrico?)
- Use
traceroutepara ver os saltos entre seu computador e Neon - Use
arp -apara ver o cache ARP local
Exercício 2: Implemente o algoritmo CSMA/CD em pseudocódigo. Simule 5 estações tentando transmitir simultaneamente — quantas colisões ocorrem antes de todas conseguirem transmitir? Use exponential backoff.
Exercício 3: Implemente um switch Ethernet completo em Python com self-learning, aging de entradas (remover MACs não vistos há 300 segundos), e suporte a broadcast. Teste com 4 hosts simulados.
Exercício 4: Calcule o CRC-32 manualmente (com a biblioteca binascii) para 3 quadros Ethernet diferentes. Depois, altere 1 bit em cada quadro e verifique se o CRC detecta o erro.
Verifique seu entendimento
Qual protocolo é usado para descobrir o MAC address de um host conhecendo apenas seu IP?
Verifique seu entendimento
No CSMA/CD, o que acontece quando uma estação detecta colisão durante a transmissão?
Verifique seu entendimento
Qual é a diferença fundamental entre um trunk port e um access port em VLANs?