#!/bin/bash # # init_eth_e2e_bridged_intermediate.sh # # Purpose: # Configure a line of Ethernet nodes for E2E PTP testing. # - Default: router mode with /32-per-port IPs + static /32 routes hop-by-hop. # - If a node is listed in --bridge-list, it becomes a pure L2 bridge (br0) between eth1↔eth2. # # Example: # ./init_eth_e2e_bridged_intermediate.sh 0 1 2 3 4 9 14 19 24 --bridge-list 1,2 # set -euo pipefail ### === CONFIG (EDIT AS NEEDED) === SUBNET_BASE="10.10.0" # A.B.C HOST_OCTET_BASE=10 # host octet = 10 + NODE_ID (apu00 → .10) IF_LEFT="eth1" # toward lower index (left) IF_RIGHT="eth2" # toward higher index (right) SET_MTU=1500 SET_TXQUEUELEN=1000 ASSIGN_MGMT_IP_ON_BRIDGE=1 # put SELF_IP/32 on br0 for bridge nodes # Helper: is a node ID in the bridge list? is_bridge_node() { local q="$1" for b in "${BRIDGE_IDS[@]}"; do [[ -z "$b" ]] && continue if [[ $q -eq $((10#$b)) ]]; then return 0; fi done return 1 } ### === ARG PARSING (nodes first, optional --bridge-list CSV last) === if [[ $# -lt 1 ]]; then echo "Usage: $0 [--bridge-list id1,id2,...]" exit 1 fi NODES=() BRIDGE_CSV="" while (( $# )); do case "$1" in --bridge-list) shift [[ $# -gt 0 ]] || { echo "ERROR: --bridge-list requires csv argument"; exit 1; } BRIDGE_CSV="$1" shift ;; *) # treat as node id if [[ "$1" =~ ^[0-9]+$ ]]; then NODES+=("$1") shift else echo "ERROR: unexpected argument: $1" exit 1 fi ;; esac done if [[ ${#NODES[@]} -eq 0 ]]; then echo "ERROR: no node IDs provided" exit 1 fi # Parse bridge list into an array BRIDGE_IDS=() if [[ -n "$BRIDGE_CSV" ]]; then IFS=',' read -r -a BRIDGE_IDS <<< "$BRIDGE_CSV" fi ### === GET SELF ID FROM HOSTNAME (expects ...00, ...01, etc.) === HOSTNAME=$(hostname) if [[ $HOSTNAME =~ ([0-9]+)$ ]]; then NODE_ID=$((10#${BASH_REMATCH[1]})) else echo "Cannot parse NODE_ID from hostname '$HOSTNAME'"; exit 1 fi ### === FIND OUR INDEX IN THE CHAIN === NODE_INDEX=-1 for idx in "${!NODES[@]}"; do if [[ $((10#${NODES[$idx]})) -eq $NODE_ID ]]; then NODE_INDEX=$idx; break; fi done if (( NODE_INDEX < 0 )); then echo "Skip: Node ID $NODE_ID not in list: ${NODES[*]}" exit 0 fi ### === DETERMINE IF WE SHOULD BE A BRIDGE === BRIDGE_THIS=0 for b in "${BRIDGE_IDS[@]}"; do [[ -z "$b" ]] && continue if [[ $NODE_ID -eq $((10#$b)) ]]; then BRIDGE_THIS=1; break; fi done ### === COMPUTE SELF & NEIGHBORS === SELF_HOST_OCTET=$((HOST_OCTET_BASE + NODE_ID)) SELF_IP="${SUBNET_BASE}.${SELF_HOST_OCTET}" HAS_LEFT=0; HAS_RIGHT=0 LEFT_ID=""; RIGHT_ID="" LEFT_IP=""; RIGHT_IP="" if (( NODE_INDEX > 0 )); then HAS_LEFT=1 LEFT_ID=$((10#${NODES[$((NODE_INDEX-1))]})) LEFT_IP="${SUBNET_BASE}.$((HOST_OCTET_BASE + LEFT_ID))" fi if (( NODE_INDEX < ${#NODES[@]} - 1 )); then HAS_RIGHT=1 RIGHT_ID=$((10#${NODES[$((NODE_INDEX+1))]})) RIGHT_IP="${SUBNET_BASE}.$((HOST_OCTET_BASE + RIGHT_ID))" fi LEFT_IS_BRIDGE=0 RIGHT_IS_BRIDGE=0 if (( HAS_LEFT )) && is_bridge_node "$LEFT_ID"; then LEFT_IS_BRIDGE=1; fi if (( HAS_RIGHT )) && is_bridge_node "$RIGHT_ID"; then RIGHT_IS_BRIDGE=1; fi # If asked to bridge but only one neighbor, fall back to router mode if (( BRIDGE_THIS )) && ! (( HAS_LEFT && HAS_RIGHT )); then echo "WARN: ${HOSTNAME} requested as BRIDGE but has only one neighbor – falling back to ROUTER mode." BRIDGE_THIS=0 fi ### === COMMON CLEANUP ON PORTS === for IF in "$IF_LEFT" "$IF_RIGHT"; do ip link set "$IF" down 2>/dev/null || true ip addr flush dev "$IF" || true ip route flush dev "$IF" || true ip neigh flush dev "$IF" || true ip link set "$IF" mtu "$SET_MTU" 2>/dev/null || true ip link set "$IF" txqueuelen "$SET_TXQUEUELEN" 2>/dev/null || true done if (( BRIDGE_THIS )); then ############################################################################## # BRIDGE MODE # ############################################################################## # Create br0 if missing if ! ip link show br0 &>/dev/null; then ip link add br0 type bridge fi # Disable STP (optional) and multicast snooping (pass 1588 L2 multicast) ip link set br0 type bridge stp_state 0 || true echo 0 > /sys/class/net/br0/bridge/multicast_snooping || true # Bring up bridge and enslave ports ip link set br0 up ip link set "$IF_LEFT" master br0 ip link set "$IF_RIGHT" master br0 ip link set "$IF_LEFT" up ip link set "$IF_RIGHT" up # Put management /32 on br0 (optional) if (( ASSIGN_MGMT_IP_ON_BRIDGE )); then ip addr flush dev br0 || true ip addr add "${SELF_IP}/24" dev br0 2>/dev/null || true fi # Ensure we are NOT routing in bridge mode sysctl -q -w net.ipv4.ip_forward=0 echo echo "=== ETH E2E L2 BRIDGE CONFIGURATION ===" echo "HOSTNAME : $HOSTNAME" echo "NODE_ID : $NODE_ID" echo "MODE : BRIDGE (br0)" echo "SELF_IP (/32 on br0): $([[ $ASSIGN_MGMT_IP_ON_BRIDGE -eq 1 ]] && echo $SELF_IP || echo 'none')" echo "NODES : ${NODES[*]}" echo "LEFT NEIGHBOR : $([[ $HAS_LEFT -eq 1 ]] && echo "$LEFT_ID ($LEFT_IP)" || echo -)" echo "RIGHT NEIGHBOR : $([[ $HAS_RIGHT -eq 1 ]] && echo "$RIGHT_ID ($RIGHT_IP)" || echo -)" echo "PORTS : $IF_LEFT,$IF_RIGHT enslaved to br0" echo "multicast_snooping : $(cat /sys/class/net/br0/bridge/multicast_snooping 2>/dev/null || echo N/A)" echo "=======================================" echo cat <<'ASCII' PTP E2E over L2 notes: - Do NOT run ptp4l/phc2sys on this node. - This bridge forwards 1588 L2 multicast (01:1B:19:00:00:00). Snooping is disabled. - Management IP (if any) is bound to br0, not to member ports. ASCII else ############################################################################## # ROUTER MODE # ############################################################################## # Assign /32 on active interfaces (same IP on both) if (( HAS_LEFT )); then ip addr add "${SELF_IP}/32" dev "$IF_LEFT" 2>/dev/null || true ip link set "$IF_LEFT" up fi if (( HAS_RIGHT )); then ip addr add "${SELF_IP}/32" dev "$IF_RIGHT" 2>/dev/null || true ip link set "$IF_RIGHT" up fi # Enable forwarding between interfaces sysctl -q -w net.ipv4.ip_forward=1 # Disable Proxy ARP; relax rp_filter for IF in "$IF_LEFT" "$IF_RIGHT"; do sysctl -q -w "net.ipv4.conf.${IF}.proxy_arp=0" sysctl -q -w "net.ipv4.conf.${IF}.rp_filter=0" done sysctl -q -w net.ipv4.conf.all.proxy_arp=0 sysctl -q -w net.ipv4.conf.all.rp_filter=0 # ARP hygiene (avoid ARP flux) for IF in "$IF_LEFT" "$IF_RIGHT"; do sysctl -q -w "net.ipv4.conf.${IF}.arp_filter=1" sysctl -q -w "net.ipv4.conf.${IF}.arp_ignore=1" sysctl -q -w "net.ipv4.conf.${IF}.arp_announce=2" done sysctl -q -w net.ipv4.conf.all.arp_filter=1 sysctl -q -w net.ipv4.conf.all.arp_ignore=1 sysctl -q -w net.ipv4.conf.all.arp_announce=2 # Neighbor on-link routes if (( HAS_LEFT )); then ip route replace "${LEFT_IP}/32" dev "$IF_LEFT" scope link src "${SELF_IP}" fi if (( HAS_RIGHT )); then ip route replace "${RIGHT_IP}/32" dev "$IF_RIGHT" scope link src "${SELF_IP}" fi # Farther routes (bridge-aware): if the immediate neighbor is a BRIDGE, # install direct on-link routes; otherwise route via the neighbor. for target_idx in "${!NODES[@]}"; do TID=$((10#${NODES[$target_idx]})) [[ "$TID" -eq "$NODE_ID" ]] && continue TARGET_IP="${SUBNET_BASE}.$((HOST_OCTET_BASE + TID))" if (( target_idx < NODE_INDEX )); then # Target lies to the LEFT if (( HAS_LEFT )) && [[ "$TID" -ne "$LEFT_ID" ]]; then if (( LEFT_IS_BRIDGE )); then ip route replace "${TARGET_IP}/32" dev "$IF_LEFT" scope link src "${SELF_IP}" else ip route replace "${TARGET_IP}/32" via "${LEFT_IP}" dev "$IF_LEFT" onlink src "${SELF_IP}" fi fi else # Target lies to the RIGHT if (( HAS_RIGHT )) && [[ "$TID" -ne "$RIGHT_ID" ]]; then if (( RIGHT_IS_BRIDGE )); then ip route replace "${TARGET_IP}/32" dev "$IF_RIGHT" scope link src "${SELF_IP}" else ip route replace "${TARGET_IP}/32" via "${RIGHT_IP}" dev "$IF_RIGHT" onlink src "${SELF_IP}" fi fi fi done # Summary LEFT_SHOW="-" ; RIGHT_SHOW="-" (( HAS_LEFT )) && LEFT_SHOW="${LEFT_ID} (${LEFT_IP})" (( HAS_RIGHT )) && RIGHT_SHOW="${RIGHT_ID} (${RIGHT_IP})" echo echo "=== ETH E2E STATIC-ROUTE CONFIGURATION ===" echo "HOSTNAME : $HOSTNAME" echo "NODE_ID : $NODE_ID" echo "MODE : ROUTER (/32 per port + static routes)" echo "SELF_IP (/32) : $SELF_IP" echo "NODES : ${NODES[*]}" echo "LEFT NEIGHBOR : $LEFT_SHOW" echo "RIGHT NEIGHBOR : $RIGHT_SHOW" echo "IF UP : $([[ $HAS_LEFT -eq 1 ]] && echo $IF_LEFT) $([[ $HAS_RIGHT -eq 1 ]] && echo $IF_RIGHT)" echo "IP FORWARDING : $(sysctl -n net.ipv4.ip_forward)" echo "Proxy ARP ($IF_LEFT): $(sysctl -n net.ipv4.conf.${IF_LEFT}.proxy_arp 2>/dev/null || echo N/A)" echo "Proxy ARP ($IF_RIGHT): $(sysctl -n net.ipv4.conf.${IF_RIGHT}.proxy_arp 2>/dev/null || echo N/A)" echo "ARP hardening (all) : filter=$(sysctl -n net.ipv4.conf.all.arp_filter), ignore=$(sysctl -n net.ipv4.conf.all.arp_ignore), announce=$(sysctl -n net.ipv4.conf.all.arp_announce)" echo "==========================================" echo cat <<'ASCII' Quick test (from an edge like apu00): ip route show | grep 10.10.0. ping -c 2 10.10.0. # e.g., 10 ping -c 2 10.10.0. # e.g., 34 If only neighbors respond, check: sysctl net.ipv4.ip_forward # must be 1 (router mode) nft list ruleset | grep -i forward # firewall may block FORWARD iptables -S FORWARD # policy should ACCEPT or allow eth1<->eth2 ip -4 neigh show dev eth1; ip -4 neigh show dev eth2 ASCII echo "[${HOSTNAME}] E2E static-route configuration complete." fi