Skip to content

Instantly share code, notes, and snippets.

@vorlon001
Created October 24, 2026 06:54
Show Gist options
  • Select an option

  • Save vorlon001/4abd7a611ccca8aea9283bf9d6dfce0a to your computer and use it in GitHub Desktop.

Select an option

Save vorlon001/4abd7a611ccca8aea9283bf9d6dfce0a to your computer and use it in GitHub Desktop.
EVPN L2 FRR GLM example v.2

Три файла

  • evpn_setup.sh — универсальный, запускается на каждой ВМ (нода сама определяет себя по IP 192.168.200.x). Ставит FRR (если нет), настраивает bridge+VXLAN+netns, генерирует frr.conf и перезапускает FRR.
  • test_evpn.sh — тест: BGP, VNI, ping'и из netns, MAC-таблицы.
  • deploy.sh — раскатка со своей рабочей машины на все 3 ноды по SSH + автотест.

Итоговая адресация:

ВМ underlay AS netns IP в netns
node130 192.168.200.130 65001 ns130 10.0.0.130/24
node131 192.168.200.131 65002 ns131 10.0.0.131/24
node132 192.168.200.132 65003 ns132 10.0.0.132/24

1. evpn_setup.sh (запуск на ВМ: sudo bash evpn_setup.sh [130|131|132] [iface])

#!/usr/bin/env bash
# evpn_setup.sh — нода L2 EVPN-VXLAN (FRR) + netns
# Использование: sudo ./evpn_setup.sh [130|131|132] [underlay-iface]
set -euo pipefail
[[ $EUID -eq 0 ]] || { echo "запускать от root"; exit 1; }

as_of() { case "$1" in 130) echo 65001;; 131) echo 65002;; 132) echo 65003;; esac; }

# ---------- номер ноды ----------
NODE="${1:-$(hostname -I | tr ' ' '\n' | awk -F. '$1==192&&$2==168&&$3==200{print $4; exit}')}"
case "$NODE" in
  130) PEERS="131 132";; 131) PEERS="130 132";; 132) PEERS="130 131";;
  *) echo "не знаю ноду '$NODE' (жду 130/131/132)"; exit 1;;
esac

IP_U="192.168.200.$NODE"; VNI=10010; VX="vxlan$VNI"; BR=br0
NS="ns$NODE"; NS_IP="10.0.0.$NODE/24"

# ---------- underlay-интерфейс ----------
IF_U="${2:-$(ip -4 -o addr show | awk -F' +|/' -v ip="$IP_U" '$4==ip{print $2; exit}')}"
[[ -n "$IF_U" ]] || IF_U=$(ip route get "$IP_U" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')
[[ -n "$IF_U" ]] || IF_U=eth1
echo ">>> node$NODE (AS $(as_of "$NODE")), underlay: $IF_U = $IP_U/24"

# ---------- sysctl ----------
modprobe vxlan 2>/dev/null || true
sysctl -qw net.ipv4.ip_forward=1
sysctl -qw net.ipv4.conf.all.rp_filter=0
sysctl -qw "net.ipv4.conf.$IF_U.rp_filter=0" 2>/dev/null || true

# ---------- underlay ----------
ip link set "$IF_U" up
ip -4 addr show dev "$IF_U" | grep -qw "$IP_U" || ip addr add "$IP_U/24" dev "$IF_U"

# ---------- bridge + VTEP ----------
ip link add "$BR" type bridge 2>/dev/null || true
ip link set "$BR" mtu 1450; ip link set "$BR" up

ip link show "$VX" &>/dev/null || ip link add "$VX" type vxlan id "$VNI" local "$IP_U" dstport 4789 nolearning
ip link set "$VX" type vxlan local "$IP_U" dstport 4789 nolearning 2>/dev/null || true
ip link set "$VX" mtu 1450; ip link set "$VX" master "$BR" 2>/dev/null || true
ip link set "$VX" up

# ---------- netns + veth ----------
ip netns list | awk '{print $1}' | grep -qx "$NS" || ip netns add "$NS"
if ! ip link show veth_h &>/dev/null; then
  ip link add veth_h type veth peer name veth_ns
  ip link set veth_h mtu 1450; ip link set veth_h master "$BR"; ip link set veth_h up
  ip link set veth_ns netns "$NS"
fi
ip -n "$NS" link set lo up
ip -n "$NS" link set veth_ns mtu 1450 up
ip -n "$NS" -4 addr show dev veth_ns | grep -qw "10.0.0.$NODE" || ip -n "$NS" addr add "$NS_IP" dev veth_ns

# ---------- firewall ----------
if command -v iptables &>/dev/null; then
  iptables -C INPUT -p udp --dport 4789 -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -p udp --dport 4789 -j ACCEPT
  iptables -C INPUT -p tcp --dport 179  -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -p tcp --dport 179 -j ACCEPT
fi

# ---------- FRR ----------
if ! command -v vtysh &>/dev/null; then
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq && apt-get install -y -qq frr
fi
touch /etc/frr/daemons
grep -q '^zebra=' /etc/frr/daemons && sed -i 's/^zebra=.*/zebra=yes/' /etc/frr/daemons || echo 'zebra=yes' >> /etc/frr/daemons
grep -q '^bgpd='  /etc/frr/daemons && sed -i 's/^bgpd=.*/bgpd=yes/'  /etc/frr/daemons || echo 'bgpd=yes'  >> /etc/frr/daemons

NEIGH_CFG=""; NEIGH_ACT=""
for p in $PEERS; do
  NEIGH_CFG+=" neighbor 192.168.200.$p remote-as $(as_of "$p")"$'\n'
  NEIGH_ACT+="  neighbor 192.168.200.$p activate"$'\n'
done

cat > /etc/frr/frr.conf <<EOF
frr version 8.4
frr defaults traditional
hostname node$NODE
log file /var/log/frr/frr.log
service integrated-vtysh-config
!
ip forwarding
!
router bgp $(as_of "$NODE")
 bgp router-id $IP_U
 no bgp ebgp-requires-policy
${NEIGH_CFG} address-family l2vpn evpn
${NEIGH_ACT}  advertise-all-vni
 exit-address-family
!
line vty
!
end
EOF
chown frr:frr /etc/frr/frr.conf /etc/frr/daemons 2>/dev/null || true
systemctl enable frr >/dev/null 2>&1 || true
systemctl restart frr

echo ">>> итог:"
ip -br addr show "$IF_U"; ip -br link show "$BR" "$VX"
ip netns list | grep "^$NS"
echo ">>> жду сходимость BGP 5 c..."; sleep 5
vtysh -c 'show bgp l2vpn evpn summary' || true
echo ">>> готово. Тест: ./test_evpn.sh $NODE"

2. test_evpn.sh (запуск на ноде: ./test_evpn.sh)

#!/usr/bin/env bash
# test_evpn.sh — проверка L2 связности netns через EVPN-VXLAN
set -uo pipefail
NODE="${1:-$(hostname -I | tr ' ' '\n' | awk -F. '$1==192&&$2==168&&$3==200{print $4; exit}')}"
case "$NODE" in 130|131|132) ;; *) echo "usage: $0 [130|131|132]"; exit 1;; esac

NS="ns$NODE"; NS_IP="10.0.0.$NODE"; VNI=10010; RC=0
section() { echo; echo "===== $* ====="; }

section "$NS ($NS_IP): BGP EVPN"
vtysh -c 'show bgp l2vpn evpn summary'

section "VNI"
vtysh -c 'show evpn vni'

section "ping из $NS"
for peer in 130 131 132; do
  [[ "$peer" == "$NODE" ]] && continue
  if ip netns exec "$NS" ping -c 3 -W 2 -q "10.0.0.$peer" >/tmp/p.$$ 2>&1; then
    echo "  OK   $NS_IP -> 10.0.0.$peer ($(grep -o '[0-9]*% packet loss' /tmp/p.$$ | head -1))"
  else
    echo "  FAIL $NS_IP -> 10.0.0.$peer"; tail -3 /tmp/p.$$; RC=1
  fi
done
rm -f /tmp/p.$$

section "ARP-кэш $NS (выученные MAC соседей)"
ip netns exec "$NS" ip neigh

section "MAC-таблица EVPN (VNI $VNI)"
vtysh -c "show evpn mac vni $VNI"

section "remote VTEP в FDB vxlan$VNI"
bridge fdb show dev vxlan$VNI | grep dst || echo "  (пусто — повторите через пару секунд после ping)"

section "итог"
[[ $RC -eq 0 ]] && echo "ВСЁ ОК" || echo "ЕСТЬ ПРОБЛЕМЫ"
exit $RC

# --- если FAIL, смотреть: udp/4789 (tcpdump -ni <if> udp port 4789),
#     tcp/179 (BGP), firewall, MTU 1450 на br0/veth, соседей Established

3. deploy.sh (со своей машины, раскатывает всё сразу)

#!/usr/bin/env bash
set -euo pipefail
NODES=(130 131 132)
DIR=$(cd "$(dirname "$0")" && pwd)
SSH_USER="${SSH_USER:-root}"          # SSH_USER=admin ./deploy.sh
RUN='bash'; [[ "$SSH_USER" == "root" ]] || RUN='sudo bash'

for n in "${NODES[@]}"; do
  h="$SSH_USER@192.168.200.$n"
  echo "===== $h: копирование ====="
  scp -q "$DIR/evpn_setup.sh" "$DIR/test_evpn.sh" "$h:/tmp/"
  echo "===== $h: настройка ====="
  ssh "$h" "$RUN /tmp/evpn_setup.sh"
done

echo "===== сходимость BGP: 10 c ====="; sleep 10

FAIL=0
for n in "${NODES[@]}"; do
  h="$SSH_USER@192.168.200.$n"
  echo "===== $h: тест ====="
  ssh "$h" "$RUN /tmp/test_evpn.sh" || FAIL=1
done
exit $FAIL

Как пользоваться

# с рабочей машины:
ssh-copy-id root@192.168.200.130 root@192.168.200.131 root@192.168.200.132
./deploy.sh

# либо вручную на каждой ВМ:
sudo bash evpn_setup.sh        # нода определится по своему IP
./test_evpn.sh

Примечания:

  • скрипт идемпотентный — можно перезапускать, ничего не задублируется;
  • если underlay-IP ещё не назначен на ВМ — передайте интерфейс явно: evpn_setup.sh 130 ens4;
  • откат на ноде: ip netns del ns130; ip link del veth_h; ip link del vxlan10010; ip link del br0;
  • первый ping может не пройти 1–2 попытки (ARP идёт как BUM, потом MAC'и уйдут в EVPN Type-2) — тест это переживает, -c 3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment