#!/bin/bash

# ovn-ns-no-veth-repro.sh
# Build OVN topology with namespaces, generate many IPv6 clients,
# and inspect OVS datapath flows for per-client IPv6 exact-match flows.
#
# Run as root. Requires: ovs, ovn-nbctl, ovn-sbctl, ip, ping, ovs-appctl
#
#    Client
#       | < veth
#    Gateway
#       | < LSP
#    External-sw
#       | < LRP
#    Router
#       | < LRP
#    Internal-sw
#       | < LSP
#    Server


set -euo pipefail

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="${SCRIPT_DIR}/single_client_ping6_reproducer.log"
OVN_NB_DB="unix:/var/run/ovn/ovnnb_db.sock"
OVN_SB_DB="unix:/var/run/ovn/ovnsb_db.sock"

EXTERNAL_BRIDGE="br-ext"
INTERNAL_BRIDGE="br-int"
ROUTER_NAME="router1"
INTERNAL_SWITCH="internal-sw"
EXTERNAL_SWITCH="external-sw"
SERVER_LSP="server-lsp"
GATEWAY_LSP="gateway-lsp"
SERVER_PORT_NAME="server-port"
CLIENT_PORT_NAME="client-port"
GATEWAY_PORT_NAME="gateway-port"
CLIENT_NS="client-ns"
SERVER_NS="server-ns"
GATEWAY_NS="gateway-ns"
NUM_IPV6_ADDRESSES=50  # Number of IPv6 addresses to test
PING_COUNT=3  # Number of ping packets per IPv6 address

EXTERNAL_IPV6_PREFIX="2001:db8:1000"
INTERNAL_IPV6_PREFIX="2001:db8:2000"
CLIENT_IPV6_PREFIX="2001:db8:3000"
ROUTER_EXTERNAL_IPV6="${EXTERNAL_IPV6_PREFIX}::1"   # 2001:db8:1000::1
ROUTER_INTERNAL_IPV6="${INTERNAL_IPV6_PREFIX}::1"   # 2001:db8:2000::1
GATEWAY_IPV6="${EXTERNAL_IPV6_PREFIX}::10"          # 2001:db8:1000::10
SERVER_IPV6="${INTERNAL_IPV6_PREFIX}::10"           # 2001:db8:2000::10

VETH_C="veth_c"
VETH_G="veth_g"
VETH_G_IPV6="$CLIENT_IPV6_PREFIX::10"               # 2001:db8:3000::10





# Logging function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

setup_ovs_bridges() {
    log "Setting up OVS bridges..."

    # Create external bridge (simulates physical network)
    ovs-vsctl add-br "$EXTERNAL_BRIDGE"
    ip link set "$EXTERNAL_BRIDGE" up
    ip addr add "${EXTERNAL_IPV6_PREFIX}::100/64" dev "$EXTERNAL_BRIDGE"
#    ip addr add "${GATEWAY_IPV6_PREFIX}::100/64" dev "$EXTERNAL_BRIDGE"

    # Enable IPv6 forwarding for the external bridge
    echo 1 > /proc/sys/net/ipv6/conf/"$EXTERNAL_BRIDGE"/forwarding
    echo 1 > /proc/sys/net/ipv6/conf/all/forwarding

    # Ensure OVN integration bridge exists
    ovs-vsctl --may-exist add-br "$INTERNAL_BRIDGE"
    ovs-vsctl set Bridge "$INTERNAL_BRIDGE" fail_mode=secure

    ovs-vsctl set Open_vSwitch . other_config:max-idle=60000

    log "OVS bridges setup completed"
}

cleanup() {
    log "Cleaning up..."

    # Remove namespaces
    ip netns del "$CLIENT_NS" 2>/dev/null || true
    ip netns del "$SERVER_NS" 2>/dev/null || true

    # Remove OVS ports
    ovs-vsctl --if-exists del-port "$INTERNAL_BRIDGE" "$SERVER_PORT_NAME"
    ovs-vsctl --if-exists del-port "$EXTERNAL_BRIDGE" "$CLIENT_PORT_NAME"

    # Remove OVN configuration
    ovn-nbctl --if-exists ls-del "$INTERNAL_SWITCH"
    ovn-nbctl --if-exists ls-del "$EXTERNAL_SWITCH"
    ovn-nbctl --if-exists lr-del "$ROUTER_NAME"

    # Remove OVS bridges
    ovs-vsctl --if-exists del-br "$EXTERNAL_BRIDGE"
    log "Cleanup completed"
}

trap cleanup EXIT

# Check dependencies
check_dependencies() {
    log "Checking dependencies..."

    local deps=("ovn-nbctl" "ovn-sbctl" "ovs-vsctl" "ovs-appctl" "ip" "ping6")
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" &> /dev/null; then
            log "ERROR: $dep is not installed or not in PATH"
            exit 1
        fi
    done

    # Check if running as root
    if [[ $EUID -ne 0 ]]; then
        log "ERROR: This script must be run as root"
        exit 1
    fi

    log "Dependencies check passed"
}

# Setup OVN logical topology
setup_ovn_topology() {
    log "Setting up OVN logical topology..."

    # Create logical switches
    ovn-nbctl ls-add "$INTERNAL_SWITCH"
    ovn-nbctl ls-add "$EXTERNAL_SWITCH"

    # Create logical router
    ovn-nbctl lr-add "$ROUTER_NAME"

    # Create router ports
    ovn-nbctl lrp-add "$ROUTER_NAME" rp-internal "02:00:00:00:01:01" "$ROUTER_INTERNAL_IPV6/64"
    ovn-nbctl lrp-add "$ROUTER_NAME" rp-external "02:00:00:00:01:02" "$ROUTER_EXTERNAL_IPV6/64"

    # Connect switches to router
    ovn-nbctl lsp-add "$INTERNAL_SWITCH" internal-to-router
    ovn-nbctl lsp-set-type internal-to-router router
    ovn-nbctl lsp-set-addresses internal-to-router "02:00:00:00:01:01"
    ovn-nbctl lsp-set-options internal-to-router router-port=rp-internal

    ovn-nbctl lsp-add "$EXTERNAL_SWITCH" external-to-router
    ovn-nbctl lsp-set-type external-to-router router
    ovn-nbctl lsp-set-addresses external-to-router "02:00:00:00:01:02"
    ovn-nbctl lsp-set-options external-to-router router-port=rp-external

    # router rule to send all traffic to rp-external (gateway)
    ovn-nbctl lr-route-add router1 "::/0" "$GATEWAY_IPV6" rp-external

    log "OVN logical topology setup completed"
}

# Create server LSP and namespace using OVS internal port
setup_server() {
    log "Setting up server using OVS internal port..."

    # Create server LSP
    ovn-nbctl lsp-add "$INTERNAL_SWITCH" "$SERVER_LSP"
    ovn-nbctl lsp-set-addresses "$SERVER_LSP" "02:00:00:00:02:01 $SERVER_IPV6"

    # Create OVS internal port for server
    ovs-vsctl add-port "$INTERNAL_BRIDGE" "$SERVER_PORT_NAME" -- \
        set Interface "$SERVER_PORT_NAME" type=internal
    ovs-vsctl set Interface "$SERVER_PORT_NAME" external-ids:iface-id="$SERVER_LSP"

    # Create server namespace and move the port
    ip netns add "$SERVER_NS"
    ip link set "$SERVER_PORT_NAME" netns "$SERVER_NS"

    # Configure server interface in namespace
    ip netns exec "$SERVER_NS" ip link set lo up
    ip netns exec "$SERVER_NS" ip link set address "02:00:00:00:02:01" dev "$SERVER_PORT_NAME"
    ip netns exec "$SERVER_NS" ip link set "$SERVER_PORT_NAME" up
    ip netns exec "$SERVER_NS" ip addr add "$SERVER_IPV6/64" dev "$SERVER_PORT_NAME"
    ip netns exec "$SERVER_NS" ip route add default via "$ROUTER_INTERNAL_IPV6"

    log "Server setup completed"
}

# Create gateway and add it to external-sw
setup_gateway(){
    log "Setting up gateway using OVS..."

    # Create gateway namespace
    ovn-nbctl lsp-add "$EXTERNAL_SWITCH" "$GATEWAY_LSP"
    ovn-nbctl lsp-set-addresses "$GATEWAY_LSP" "02:00:00:00:02:02 $GATEWAY_IPV6"

    # Create OVS internal port for server
    ovs-vsctl add-port "$INTERNAL_BRIDGE" "$GATEWAY_PORT_NAME" -- \
        set Interface "$GATEWAY_PORT_NAME" type=internal
    ovs-vsctl set Interface "$GATEWAY_PORT_NAME" external-ids:iface-id="$GATEWAY_LSP"

    # Create gateway namespace and move the port
    ip netns add "$GATEWAY_NS"
    ip link set "$GATEWAY_PORT_NAME" netns "$GATEWAY_NS"
    ip netns exec "$GATEWAY_NS" ip link set address "02:00:00:00:02:02" dev "$GATEWAY_PORT_NAME"
    ip netns exec "$GATEWAY_NS" ip link set "$GATEWAY_PORT_NAME" up
    ip netns exec "$GATEWAY_NS" ip addr add "$GATEWAY_IPV6/64" dev "$GATEWAY_PORT_NAME"

    # Add veth interface to gateway namespace and connect it to client
    ip link add "$VETH_G" type veth peer name veth_c
    ip link set "$VETH_G" netns "$GATEWAY_NS"
#    ip link set veth_c netns "$CLIENT_NS"
    ip netns exec "$GATEWAY_NS" ip link set "$VETH_G" up
    ip netns exec "$GATEWAY_NS" ip addr add "$VETH_G_IPV6/64" dev "$VETH_G"

    ip netns exec "$GATEWAY_NS" ip -6 route add default via "$ROUTER_EXTERNAL_IPV6"
    ip netns exec "$GATEWAY_NS" sysctl -w net.ipv6.conf.all.forwarding=1

    log "Gateway setup completed"
}

# Create client
setup_client(){
    log "Setting up single client namespace with $NUM_IPV6_ADDRESSES IPv6 addresses..."

    # Create client namespace
    ip netns add "$CLIENT_NS"
    ip link set "$VETH_C" netns "$CLIENT_NS"
    ip netns exec "$CLIENT_NS" ip link set "$VETH_C" up
    ip netns exec "$CLIENT_NS" ip addr add 2001:db8:3000::20/64 dev "$VETH_C"
    ip netns exec "$CLIENT_NS" ip -6 route add default via "$VETH_G_IPV6"

    # Create OVS internal port connected to external bridge
    ovs-vsctl add-port "$EXTERNAL_BRIDGE" "$CLIENT_PORT_NAME" -- \
        set Interface "$CLIENT_PORT_NAME" type=internal

    # Move port to client namespace and configure
    ip link set "$CLIENT_PORT_NAME" netns "$CLIENT_NS"
    ip netns exec "$CLIENT_NS" ip link set lo up
#    ip netns exec "$CLIENT_NS" ip link set address "02:00:00:00:02:02" dev "$CLIENT_PORT_NAME"
    ip netns exec "$CLIENT_NS" ip link set "$CLIENT_PORT_NAME" up

    # Add multiple IPv6 addresses to the client interface
    for i in $(seq 1 $NUM_IPV6_ADDRESSES); do
        local client_ipv6="${CLIENT_IPV6_PREFIX}::$(printf "%x" $((0x1000 + i)))"
        # ip netns exec "$CLIENT_NS" ip addr add "$client_ipv6/64" dev "$CLIENT_PORT_NAME"
        ip netns exec "$CLIENT_NS" ip addr add "$client_ipv6/64" dev "$VETH_C"
        log "Added IPv6 address $client_ipv6 to client interface"
    done

    log "Client setup completed with $NUM_IPV6_ADDRESSES IPv6 addresses and veth to gateway"
}

# Test connectivity
test_connectivity() {
    log "Testing basic connectivity..."

    local test_client_ns="$CLIENT_NS"
    local test_server_ns="$SERVER_NS"
    local test_gateway_ns="$GATEWAY_NS"

    echo "=== Connectivity Test Results ==="

    # Test 1: Client namespace network setup
    echo "1. Client network configuration:"
    ip netns exec "$test_client_ns" ip -6 addr show 2>/dev/null || echo "   ❌ Client namespace not found"

    # Test 2: External namesapce network setup
    echo "2. External network configuration:"
    ip netns exec "$test_gateway_ns" ip -6 addr show 2>/dev/null || echo "   ❌ External namespace not found"

    # Test 3: Server namespace network setup
    echo "3. Server network configuration:"
    ip netns exec "$test_server_ns" ip -6 addr show 2>/dev/null || echo "   ❌ Server namespace not found"

    # Test 4: OVN logical topology
    echo "4. OVN logical switches:"
    ovn-nbctl ls-list

    echo "5. OVN logical switch ports:"
    ovn-nbctl show

    # Test 6: Basic ping test to router
    echo "6. Client to router connectivity:"
    if ip netns exec "$test_client_ns" ping6 -c 2 -W 3 "$ROUTER_EXTERNAL_IPV6" >/dev/null 2>&1; then
        echo "   ✅ Client can ping router external IP ($ROUTER_EXTERNAL_IPV6)"
    else
        echo "   ❌ Client cannot ping router external IP"
        echo "   Client routing table:"
        ip netns exec "$test_client_ns" ip -6 route show
    fi

    # Test 7: Try ping6 to server from first IPv6 address
    echo "7. Direct client-server ping6 test:"
    local first_ipv6="${CLIENT_IPV6_PREFIX}::1001"
    local test_result

    if timeout 10 ip netns exec "$test_client_ns" ping6 -c 1 -W 3 -I "$first_ipv6" "$SERVER_IPV6" >/dev/null 2>&1; then
        echo "   ✅ Client can ping6 server from $first_ipv6"
        test_result="PASS"
    else
        echo "   ❌ Client cannot ping6 server from $first_ipv6"
        test_result="FAIL"
    fi

    echo ""
    echo "Overall connectivity test: $test_result"
    echo "=========================="

    return $([[ "$test_result" == "PASS" ]] && echo 0 || echo 1)
}

# Generate ping6 traffic from multiple IPv6 addresses
generate_ping6_traffic() {
    log "Generating ping6 traffic from $NUM_IPV6_ADDRESSES IPv6 addresses..."

    # Clear existing datapath flows to start fresh
    ovs-appctl dpctl/del-flows

    log "Cleared existing datapath flows"
    sleep 2

    # Generate ping6 traffic from each IPv6 address
    local pids=()
    for i in $(seq 1 $NUM_IPV6_ADDRESSES); do
        local client_ipv6="${CLIENT_IPV6_PREFIX}::$(printf "%x" $((0x1000 + i)))"

        # Generate ping6 with specific source IP
        timeout 10 ip netns exec "$CLIENT_NS" ping6 -c "$PING_COUNT" -W 3 -I "$client_ipv6" "$SERVER_IPV6" >/dev/null 2>&1 &
        pids+=($!)

        # Small delay to spread out connections
        sleep 0.1
    done

    # Wait for all background ping6 processes to complete
    log "Waiting for $NUM_IPV6_ADDRESSES ping6 processes to complete..."
    local wait_time=15
    sleep $wait_time

    # Kill any remaining processes
    local remaining_count=0
    for pid in "${pids[@]}"; do
        if kill -0 "$pid" 2>/dev/null; then
            log "Killing remaining process $pid"
            kill "$pid" 2>/dev/null || true
            ((remaining_count++))
        fi
    done

    if [[ $remaining_count -gt 0 ]]; then
        log "⚠️  Killed $remaining_count remaining processes after timeout"
    fi

    log "Ping6 traffic generation completed"
}

# Analyze datapath flows
analyze_datapath_flows() {
    log "Analyzing datapath flows..."

    local flows_file="${SCRIPT_DIR}/single_client_ping6_datapath_flows.txt"
    local analysis_file="${SCRIPT_DIR}/single_client_ping6_flow_analysis.txt"

    # Allow flows to stabilize
    sleep 3

    # Dump all datapath flows
    ovs-appctl dpctl/dump-flows > "$flows_file"

    # Count total flows
    local total_flows=$(wc -l < "$flows_file")
    log "Total datapath flows: $total_flows"

    if [[ $total_flows -gt 10 ]]; then
        log "❌ Moderate number of flows, this needs to be checked"
    else
        log "✅ Flow count is reasonable."
    fi
}



# Wait for OVN convergence
wait_for_convergence() {
    log "Waiting for OVN to converge..."

    local max_wait=30
    local wait_count=0

    while [[ $wait_count -lt $max_wait ]]; do
        # Check if logical flows are installed
        local logical_flows=$(ovn-sbctl dump-flows | wc -l)

        if [[ $logical_flows -gt 10 ]]; then
            log "OVN converged with $logical_flows logical flows"
            break
        fi

        sleep 1
        ((wait_count++))
    done

    if [[ $wait_count -eq $max_wait ]]; then
        log "WARNING: OVN may not have fully converged"
    fi
}

# Display OVN/OVS version information
show_versions() {
    log "Displaying version information..."

    echo "=== Software Versions ==="
    echo "Kernel:"
    uname -r
    echo "OVS:"
    rpm -qa | grep -E "openvswitch"
    echo "OVN:"
    rpm -qa | grep -E "ovn"
}

# Main execution
main() {
    log "Starting Single Client IPv6 Ping6 datapath flow reproducer for FDP-1557"

    show_versions
    #check_dependencies

    log "Setting up environment..."
    setup_ovs_bridges
    setup_ovn_topology
    wait_for_convergence
    setup_server
    setup_gateway
    setup_client

    log "Environment setup completed. Running connectivity test..."

    # Test connectivity first
    if test_connectivity; then
        log "✅ Connectivity test passed. Proceeding with ping6 traffic generation..."
    else
        log "❌ Connectivity test failed. Check the output above for issues."
        log "You can still try the demonstration, but results may not be meaningful."
        read -p "Continue anyway? (y/N): " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            log "Exiting due to connectivity issues."
            exit 1
        fi
    fi

    log "Starting ping6 traffic generation from $NUM_IPV6_ADDRESSES IPv6 addresses..."
    generate_ping6_traffic

    log "Running analysis..."
    analyze_datapath_flows

    log "Reproducer completed successfully"
}

# Help function
show_help() {
    echo "Single Client IPv6 Ping6 Datapath Flow Issue Reproducer (FDP-1557)"
    echo ""
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  -h, --help     Show this help message"
    echo "  -n, --num-ips  Number of IPv6 addresses to test (default: $NUM_IPV6_ADDRESSES)"
    echo "  -c, --count    Number of ping packets per IPv6 address (default: $PING_COUNT)"
    echo "  --cleanup-only Only run cleanup and exit"
    echo "  --test-only    Only run connectivity test"
    echo ""
    echo "This script reproduces the issue where OVN creates excessive datapath flows"
    echo "for IPv6 traffic using a single client namespace with multiple IPv6 addresses"
    echo "and ping6 instead of HTTP requests."
    echo ""
    echo "Requirements:"
    echo "- Must run as root"
    echo "- OVN and OVS must be installed and running"
    echo "- IPv6 support enabled in kernel"
    echo "- ping6 command available"
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -h|--help)
            show_help
            exit 0
            ;;
        -n|--num-ips)
            NUM_IPV6_ADDRESSES="$2"
            shift 2
            ;;
        -c|--count)
            PING_COUNT="$2"
            shift 2
            ;;
        --cleanup-only)
            cleanup
            exit 0
            ;;
        --test-only)
            # Set up minimal environment for connectivity test
#            show_versions
#            check_dependencies
            setup_ovs_bridges
            setup_ovn_topology
            wait_for_convergence
            setup_server
            setup_gateway
            setup_client
            test_connectivity
            exit $?
            ;;
        *)
            echo "Unknown option: $1"
            show_help
            exit 1
            ;;
    esac
done
# Validate NUM_IPV6_ADDRESSES
if ! [[ "$NUM_IPV6_ADDRESSES" =~ ^[0-9]+$ ]] || [[ $NUM_IPV6_ADDRESSES -lt 1 ]] || [[ $NUM_IPV6_ADDRESSES -gt 1000 ]]; then
    log "ERROR: NUM_IPV6_ADDRESSES must be a number between 1 and 1000"
    exit 1
fi

# Validate PING_COUNT
if ! [[ "$PING_COUNT" =~ ^[0-9]+$ ]] || [[ $PING_COUNT -lt 1 ]] || [[ $PING_COUNT -gt 10 ]]; then
    log "ERROR: PING_COUNT must be a number between 1 and 10"
    exit 1
fi

# Run main function
main
