CloudRouterDemo

This commit is contained in:
ayurishchev committed 2025-11-17 14:19:38 +03:00
commit 62c86b96ff
23 files changed
+1382

No files matched your search

+434
View File
@@ -0,0 +1,434 @@
#!/bin/bash
set -e
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/network-config.log
}
log "Starting network configuration..."
# Validate required commands exist
for cmd in ip netplan systemctl; do
if ! command -v "$cmd" &> /dev/null; then
log "ERROR: Required command '$cmd' is not available"
exit 1
fi
done
# Validate directories exist
if [ ! -d "/etc/netplan" ]; then
log "ERROR: /etc/netplan directory does not exist"
exit 1
fi
if [ ! -d "/sys/class/net" ]; then
log "ERROR: /sys/class/net directory does not exist"
exit 1
fi
# Enhanced function to check if IP is in private subnet (RFC 1918)
is_private_ip() {
local ip="$1"
local clean_ip=$(echo "$ip" | cut -d'/' -f1) # Remove subnet mask if present
# Check RFC 1918 private ranges:
# 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
# 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
# 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
if [[ $clean_ip =~ ^10\. ]]; then
return 0 # 10.0.0.0/8
elif [[ $clean_ip =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]]; then
return 0 # 172.16.0.0/12
elif [[ $clean_ip =~ ^192\.168\. ]]; then
return 0 # 192.168.0.0/16
else
return 1 # Is public IP
fi
}
# Function to convert CIDR to netmask
cidr_to_netmask() {
local cidr="$1"
# Input validation
if [[ ! $cidr =~ ^[0-9]+$ ]] || [ "$cidr" -lt 0 ] || [ "$cidr" -gt 32 ]; then
echo "Error: CIDR must be a number between 0 and 32" >&2
return 1
fi
local netmask=""
local full_octets=$((cidr / 8))
local remaining_bits=$((cidr % 8))
local partial_octet=0
# Calculate partial octet if there are remaining bits
if [ "$remaining_bits" -gt 0 ]; then
partial_octet=$((256 - (256 >> remaining_bits)))
fi
for ((i=0; i<4; i++)); do
if [ "$i" -lt "$full_octets" ]; then
netmask="${netmask}255"
elif [ "$i" -eq "$full_octets" ] && [ "$remaining_bits" -gt 0 ]; then
netmask="${netmask}${partial_octet}"
else
netmask="${netmask}0"
fi
if [ "$i" -lt 3 ]; then
netmask="${netmask}."
fi
done
echo "$netmask"
}
# Function to convert netmask to CIDR
netmask_to_cidr() {
local netmask="$1"
local cidr=0
# Use -a for array, not -o
IFS='.' read -ra octets <<< "$netmask"
for octet in "${octets[@]}"; do
case $octet in
255) cidr=$((cidr + 8)) ;;
254) cidr=$((cidr + 7)) ;;
252) cidr=$((cidr + 6)) ;;
248) cidr=$((cidr + 5)) ;;
240) cidr=$((cidr + 4)) ;;
224) cidr=$((cidr + 3)) ;;
192) cidr=$((cidr + 2)) ;;
128) cidr=$((cidr + 1)) ;;
0) ;;
*) echo "32"; return 1 ;; # Invalid netmask, default to /32
esac
done
echo "$cidr"
}
# Function to extract IP, netmask, and CIDR from CIDR notation
get_ip_netmask_cidr() {
local cidr_ip="$1"
local ip=$(echo "$cidr_ip" | cut -d'/' -f1)
local cidr_part=$(echo "$cidr_ip" | cut -d'/' -f2)
local netmask=""
local cidr=""
if [[ $cidr_part =~ ^[0-9]{1,2}$ ]]; then
# CIDR notation (e.g., /24)
cidr="$cidr_part"
netmask=$(cidr_to_netmask "$cidr")
elif [[ $cidr_part =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Already in netmask format
netmask="$cidr_part"
cidr=$(netmask_to_cidr "$netmask")
else
# Default to /32 if no valid netmask found
cidr="32"
netmask="255.255.255.255"
fi
echo "$ip,$netmask,$cidr"
}
# Function to extract the first private IPv4 address, netmask, and CIDR from an interface
get_private_ip_netmask_cidr() {
local interface="$1"
local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
if [ -n "$ip_addrs" ]; then
while IFS= read -r ip; do
if [ -n "$ip" ] && is_private_ip "$ip"; then
# Return IP, netmask, and CIDR
get_ip_netmask_cidr "$ip"
return 0
fi
done <<< "$ip_addrs"
fi
return 1
}
# Function to extract the first public IPv4 address, netmask, and CIDR from an interface
get_public_ip_netmask_cidr() {
local interface="$1"
local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
if [ -n "$ip_addrs" ]; then
while IFS= read -r ip; do
if [ -n "$ip" ] && ! is_private_ip "$ip"; then
# Return IP, netmask, and CIDR
get_ip_netmask_cidr "$ip"
return 0
fi
done <<< "$ip_addrs"
fi
return 1
}
# Function to get default gateway for an interface
get_interface_gateway() {
local interface="$1"
# Try to get gateway from route table for the specific interface
local gateway=$(ip route show dev "$interface" 2>/dev/null | grep '^default via' | awk '{print $3}' | head -n1)
if [ -n "$gateway" ]; then
echo "$gateway"
return 0
fi
# Fallback: get default gateway from main route table
gateway=$(ip route show 2>/dev/null | grep '^default via' | awk '{print $3}' | head -n1)
if [ -n "$gateway" ]; then
echo "$gateway"
return 0
fi
return 1
}
# Enhanced function to check if interface has private IP
has_private_ip() {
local interface="$1"
local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
if [ -n "$ip_addrs" ]; then
while IFS= read -r ip; do
if [ -n "$ip" ] && is_private_ip "$ip"; then
return 0 # Has at least one private IP
fi
done <<< "$ip_addrs"
fi
return 1 # No private IP
}
# Identify LAN and WAN interfaces with enhanced logic
LAN_IFACE=""
LAN_MAC=""
LAN_IFACE_IPv4=""
LAN_NETMASK=""
LAN_CIDR=""
WAN_IFACE=""
WAN_MAC=""
WAN_IFACE_IPv4=""
WAN_NETMASK=""
WAN_CIDR=""
WAN_GW_IPv4=""
# First pass: Look for interfaces with private IPs (LAN candidates)
for iface in $(ls /sys/class/net/ | grep -v lo); do
if has_private_ip "$iface"; then
if [ -z "$LAN_IFACE" ]; then
LAN_IFACE="$iface"
LAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
# Get IP, netmask, and CIDR
lan_ip_netmask_cidr=$(get_private_ip_netmask_cidr "$iface")
if [ -n "$lan_ip_netmask_cidr" ]; then
LAN_IFACE_IPv4=$(echo "$lan_ip_netmask_cidr" | cut -d',' -f1)
LAN_NETMASK=$(echo "$lan_ip_netmask_cidr" | cut -d',' -f2)
LAN_CIDR=$(echo "$lan_ip_netmask_cidr" | cut -d',' -f3)
fi
log "Identified LAN interface: $iface (MAC: $LAN_MAC) with private IP: $LAN_IFACE_IPv4, Netmask: $LAN_NETMASK, CIDR: /$LAN_CIDR"
else
log "Multiple LAN interface candidates found: $LAN_IFACE and $iface"
fi
fi
done
# Second pass: Look for WAN interface
for iface in $(ls /sys/class/net/ | grep -v lo); do
# Skip if this is already identified as LAN
[ "$iface" = "$LAN_IFACE" ] && continue
ip_addrs=$(ip addr show "$iface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
if [ -n "$ip_addrs" ]; then
# Check if interface has public IPs
has_public="false"
while IFS= read -r ip; do
if [ -n "$ip" ] && ! is_private_ip "$ip"; then
has_public="true"
break
fi
done <<< "$ip_addrs"
if [ "$has_public" = "true" ]; then
WAN_IFACE="$iface"
WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
# Get both IP, netmask, and CIDR for WAN
wan_ip_netmask_cidr=$(get_public_ip_netmask_cidr "$iface")
if [ -n "$wan_ip_netmask_cidr" ]; then
WAN_IFACE_IPv4=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f1)
WAN_NETMASK=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f2)
WAN_CIDR=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f3)
fi
WAN_GW_IPv4=$(get_interface_gateway "$iface")
log "Identified WAN interface: $iface (MAC: $WAN_MAC) with public IP: $WAN_IFACE_IPv4, Netmask: $WAN_NETMASK, CIDR: /$WAN_CIDR, Gateway: $WAN_GW_IPv4"
break
fi
else
# Interface with no IP - potential WAN candidate
if [ -z "$WAN_IFACE" ]; then
WAN_IFACE="$iface"
WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
WAN_GW_IPv4=$(get_interface_gateway "$iface")
log "Identified WAN interface candidate: $iface (MAC: $WAN_MAC) - no IP assigned, Gateway: $WAN_GW_IPv4"
fi
fi
done
# If no WAN found but we have LAN, pick first non-LAN interface
if [ -z "$WAN_IFACE" ] && [ -n "$LAN_IFACE" ]; then
for iface in $(ls /sys/class/net/ | grep -v lo); do
if [ "$iface" != "$LAN_IFACE" ]; then
WAN_IFACE="$iface"
WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null)
WAN_GW_IPv4=$(get_interface_gateway "$iface")
log "Assumed WAN interface: $iface (MAC: $WAN_MAC) - default selection, Gateway: $WAN_GW_IPv4"
break
fi
done
fi
# Final assignment and logging
log "Final interface assignment:"
log " LAN Interface: $LAN_IFACE (MAC: $LAN_MAC)"
if [ -n "$LAN_IFACE_IPv4" ]; then
log " LAN IPv4: $LAN_IFACE_IPv4"
log " LAN Netmask: $LAN_NETMASK"
log " LAN CIDR: /$LAN_CIDR"
else
log " LAN IPv4: Not assigned"
LAN_IFACE_IPv4="" # Ensure it's empty if no IP found
LAN_NETMASK="" # Ensure netmask is also empty
LAN_CIDR="" # Ensure CIDR is also empty
fi
if [ -n "$WAN_IFACE" ]; then
# Only set WAN IP, Netmask, CIDR and Gateway if WAN interface is detected
log " WAN Interface: $WAN_IFACE (MAC: $WAN_MAC)"
if [ -n "$WAN_IFACE_IPv4" ]; then
log " WAN IPv4: $WAN_IFACE_IPv4"
log " WAN Netmask: $WAN_NETMASK"
log " WAN CIDR: /$WAN_CIDR"
else
log " WAN IPv4: Not assigned"
log " WAN Netmask: Not available"
log " WAN CIDR: Not available"
WAN_IFACE_IPv4="" # Ensure it's empty if no IP found
WAN_NETMASK="" # Ensure netmask is also empty
WAN_CIDR="" # Ensure CIDR is also empty
fi
if [ -n "$WAN_GW_IPv4" ]; then
log " WAN Gateway: $WAN_GW_IPv4"
else
log " WAN Gateway: Not detected"
WAN_GW_IPv4="" # Ensure it's empty if no gateway found
fi
else
log " WAN Interface: Not detected"
# Ensure WAN-related variables are empty
WAN_IFACE_IPv4=""
WAN_NETMASK=""
WAN_CIDR=""
WAN_GW_IPv4=""
fi
# Validate that we have the required information before proceeding
if [ -z "$WAN_MAC" ] || [ -z "$WAN_IFACE_IPv4" ] || [ -z "$WAN_CIDR" ] || [ -z "$WAN_GW_IPv4" ] || [ -z "$LAN_MAC" ] || [ -z "$LAN_IFACE_IPv4" ] || [ -z "$LAN_CIDR" ]; then
log "ERROR: Required network information is missing. Cannot proceed with network configuration."
log "Missing information:"
[ -z "$WAN_MAC" ] && log " - WAN MAC address"
[ -z "$WAN_IFACE_IPv4" ] && log " - WAN IP address"
[ -z "$WAN_CIDR" ] && log " - WAN CIDR"
[ -z "$WAN_GW_IPv4" ] && log " - WAN Gateway"
[ -z "$LAN_MAC" ] && log " - LAN MAC address"
[ -z "$LAN_IFACE_IPv4" ] && log " - LAN IP address"
[ -z "$LAN_CIDR" ] && log " - LAN CIDR"
exit 1
fi
# Create backup of existing netplan config if it exists
if [ -f "/etc/netplan/50-cloud-init.yaml" ]; then
cp "/etc/netplan/50-cloud-init.yaml" "/etc/netplan/50-cloud-init.yaml.backup.$(date +%s)"
log "Backed up existing netplan configuration"
fi
# Disabling Cloud-Init
log "Disabling Cloud-Init for Networking..."
cat > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg <<'EOF'
network: {config: disabled}
EOF
log "Creating network config file"
# Creating netplan config with proper validation
cat > /etc/netplan/50-cloud-init.yaml << EOF
network:
version: 2
ethernets:
wan-iface:
match:
macaddress: "$WAN_MAC"
set-name: eth0
dhcp4: false
addresses:
- $WAN_IFACE_IPv4/$WAN_CIDR
routes:
- to: default
via: $WAN_GW_IPv4
nameservers:
addresses: [8.8.8.8, 1.1.1.1]
lan-iface:
match:
macaddress: "$LAN_MAC"
set-name: eth1
dhcp4: false
addresses:
- $LAN_IFACE_IPv4/$LAN_CIDR
EOF
log "Network config file has been created"
# Test the netplan configuration before applying
if netplan --debug generate; then
log "Netplan configuration generated successfully"
# Apply the configuration
netplan apply
systemctl restart systemd-networkd
# Wait a moment for network to come up
sleep 2
# Test connectivity to gateway
if ping -c 1 -W 5 "$WAN_GW_IPv4" >/dev/null 2>&1; then
log "Network configuration applied successfully! Gateway is reachable."
# Ask for confirmation before rebooting (optional safety measure)
log "Network configuration applied. Rebooting in 10 seconds. Press Ctrl+C to cancel."
sleep 10
reboot
else
log "WARNING: Gateway is not reachable after configuration. Not rebooting to prevent lockout."
log "Please check the network configuration manually."
exit 1
fi
else
log "ERROR: Netplan configuration failed to generate. Rolling back changes."
# Note: In a real scenario, you'd want to restore the backup here
exit 1
fi