489 lines
No EOL
17 KiB
Bash
489 lines
No EOL
17 KiB
Bash
#!/bin/bash
|
|
|
|
# Colors and formatting
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
BLUE='\033[0;34m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
BOLD='\033[1m'
|
|
|
|
# Default essential ports
|
|
DEFAULT_TCP_PORT=22
|
|
DEFAULT_UDP_PORT=41641
|
|
|
|
# Check if running as root
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Please run as root"
|
|
exit 1
|
|
fi
|
|
|
|
# Check for iptables-persistent installation
|
|
check_iptables_persistent() {
|
|
if ! dpkg -l | grep -q "iptables-persistent"; then
|
|
echo -e "${YELLOW}[INFO]${NC} iptables-persistent is not installed"
|
|
read -p $'\033[0;36mWould you like to install iptables-persistent? [Y/n]: \033[0m' install_choice
|
|
install_choice=${install_choice:-Y}
|
|
|
|
if [[ $install_choice =~ ^[Yy]$ ]]; then
|
|
# Pre-seed the debconf database to avoid interactive prompt
|
|
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections
|
|
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections
|
|
|
|
apt-get update
|
|
if apt-get install -y iptables-persistent; then
|
|
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} iptables-persistent installed successfully"
|
|
else
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to install iptables-persistent"
|
|
return 1
|
|
fi
|
|
else
|
|
echo -e "${YELLOW}[WARNING]${NC} Continuing without iptables-persistent"
|
|
return 1
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Enhanced save function with iptables-persistent support
|
|
save_iptables() {
|
|
echo -e "${YELLOW}[INFO]${NC} Saving iptables rules..."
|
|
|
|
# Create backup directory if it doesn't exist
|
|
mkdir -p /etc/iptables/backup
|
|
|
|
# Create timestamped backup
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
|
backup_file="/etc/iptables/backup/rules.v4.${timestamp}"
|
|
|
|
# Backup current rules
|
|
if ! iptables-save > "$backup_file"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create backup"
|
|
return 1
|
|
fi
|
|
|
|
# Try saving with netfilter-persistent first
|
|
if command -v netfilter-persistent &>/dev/null; then
|
|
if netfilter-persistent save; then
|
|
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules saved using netfilter-persistent"
|
|
echo -e "${GREEN}[INFO]${NC} Backup saved to: ${backup_file}"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Fallback to manual save
|
|
if [ -d "/etc/iptables" ]; then
|
|
if iptables-save > /etc/iptables/rules.v4; then
|
|
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules saved to /etc/iptables/rules.v4"
|
|
echo -e "${GREEN}[INFO]${NC} Backup saved to: ${backup_file}"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to save iptables rules"
|
|
return 1
|
|
}
|
|
|
|
# Enhanced restore function with iptables-persistent support
|
|
restore_iptables() {
|
|
echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous rules..."
|
|
|
|
# Try restoring from backup first
|
|
local latest_backup=$(ls -t /etc/iptables/backup/rules.v4.* 2>/dev/null | head -n1)
|
|
|
|
if [ -n "$latest_backup" ]; then
|
|
echo -e "${YELLOW}[INFO]${NC} Restoring from latest backup: ${latest_backup}"
|
|
if iptables-restore < "$latest_backup"; then
|
|
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules restored from backup"
|
|
# Save the restored rules
|
|
save_iptables
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Fallback to default rules file
|
|
if [ -f /etc/iptables/rules.v4 ]; then
|
|
echo -e "${YELLOW}[INFO]${NC} Attempting to restore from /etc/iptables/rules.v4"
|
|
if iptables-restore < /etc/iptables/rules.v4; then
|
|
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules restored from default file"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore iptables rules"
|
|
return 1
|
|
}
|
|
|
|
# Add this near the beginning of the script execution
|
|
check_iptables_persistent
|
|
|
|
# Function to print section header
|
|
print_header() {
|
|
echo -e "\n${BLUE}${BOLD}$1${NC}"
|
|
echo -e "${BLUE}${BOLD}$(printf '=%.0s' {1..50})${NC}\n"
|
|
}
|
|
|
|
# Function to display current NAT configuration
|
|
display_current_config() {
|
|
print_header "Current NAT Configuration"
|
|
|
|
echo -e "${CYAN}PREROUTING Rules (DNAT):${NC}"
|
|
if ! iptables -t nat -L PREROUTING -n -v | grep -q "DNAT"; then
|
|
echo -e "${YELLOW}No DNAT rules configured${NC}"
|
|
else
|
|
iptables -t nat -L PREROUTING -n -v | grep "DNAT"
|
|
fi
|
|
|
|
echo -e "\n${CYAN}POSTROUTING Rules (SNAT/MASQUERADE):${NC}"
|
|
if ! iptables -t nat -L POSTROUTING -n -v | grep -q "SNAT\|MASQUERADE"; then
|
|
echo -e "${YELLOW}No SNAT/MASQUERADE rules configured${NC}"
|
|
else
|
|
iptables -t nat -L POSTROUTING -n -v | grep "SNAT\|MASQUERADE"
|
|
fi
|
|
|
|
echo -e "\n${CYAN}Forward Rules:${NC}"
|
|
if ! iptables -L FORWARD -n -v | grep -q "ACCEPT\|DROP"; then
|
|
echo -e "${YELLOW}No specific forwarding rules configured${NC}"
|
|
else
|
|
iptables -L FORWARD -n -v | grep "ACCEPT\|DROP"
|
|
fi
|
|
|
|
echo # Empty line for spacing
|
|
}
|
|
|
|
# Function to get IP and interface pairs (excluding localhost)
|
|
get_interfaces() {
|
|
ip -4 addr | awk '/inet / && !/127.0.0.1/ {print $2 "\t" $NF}' | sed 's/\/[0-9]*//g'
|
|
}
|
|
|
|
# Get interface name from selected IP
|
|
get_interface_name() {
|
|
local ip=$1
|
|
ip -4 addr | grep "$ip" | awk '{print $NF}'
|
|
}
|
|
|
|
# Function to get Tailscale IPs
|
|
get_tailscale_devices() {
|
|
tailscale status | grep -v '^#' | awk 'NF>1 {print $2 " - " $1}'
|
|
}
|
|
|
|
# Function to get interface details for Tailscale device
|
|
get_tailscale_device_name() {
|
|
local ip=$1
|
|
tailscale status | grep "$ip" | awk '{print $2}'
|
|
}
|
|
|
|
# Function to get subnet from IP
|
|
get_subnet() {
|
|
local ip=$1
|
|
ip -4 addr | grep "$ip" | awk '{print $2}'
|
|
}
|
|
|
|
# Display current configuration at startup
|
|
display_current_config
|
|
|
|
# Step 1: Get source IP
|
|
print_header "Step 1: Select source IP address"
|
|
echo -e "${CYAN}Available interfaces:${NC}"
|
|
echo -e "${YELLOW}0)${NC} Enter custom IP"
|
|
|
|
# Display interface options
|
|
declare -a interfaces
|
|
while IFS=$'\t' read -r ip interface; do
|
|
interfaces+=("$ip - $interface")
|
|
done < <(get_interfaces)
|
|
|
|
for i in "${!interfaces[@]}"; do
|
|
echo -e "${YELLOW}$((i + 1)))${NC} ${interfaces[$i]}"
|
|
done
|
|
echo -e "${YELLOW}C)${NC} Cancel"
|
|
echo -e "${CYAN}Press Enter to select option 1${NC}"
|
|
|
|
read -p $'\033[0;36mSelect an option: \033[0m' choice
|
|
|
|
# Default to option 1 if Enter is pressed
|
|
choice=${choice:-1}
|
|
|
|
case $choice in
|
|
0)
|
|
read -p $'\033[0;36mEnter custom IP: \033[0m' source_ip
|
|
source_subnet=$(get_subnet "$source_ip")
|
|
;;
|
|
[1-9]*)
|
|
if [ "$choice" -le "${#interfaces[@]}" ]; then
|
|
source_ip=$(echo "${interfaces[$((choice - 1))]}" | cut -d' ' -f1)
|
|
source_subnet=$(get_subnet "$source_ip")
|
|
else
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
|
|
exit 1
|
|
fi
|
|
;;
|
|
[Cc])
|
|
echo -e "${YELLOW}Operation cancelled${NC}"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Step 2: Get target IP
|
|
print_header "Step 2: Select target Tailscale IP address"
|
|
echo -e "${CYAN}Available Tailscale devices:${NC}"
|
|
echo -e "${YELLOW}0)${NC} Enter custom IP"
|
|
|
|
# Display Tailscale devices
|
|
declare -a tailscale_devices
|
|
mapfile -t tailscale_devices < <(get_tailscale_devices)
|
|
|
|
for i in "${!tailscale_devices[@]}"; do
|
|
echo -e "${YELLOW}$((i + 1)))${NC} ${tailscale_devices[$i]}"
|
|
done
|
|
echo -e "${YELLOW}C)${NC} Cancel"
|
|
echo -e "${CYAN}Press Enter to select option 0 (custom IP)${NC}"
|
|
|
|
read -p $'\033[0;36mSelect an option: \033[0m' choice
|
|
|
|
# Default to option 0 if Enter is pressed
|
|
choice=${choice:-0}
|
|
|
|
case $choice in
|
|
0)
|
|
read -p $'\033[0;36mEnter custom IP: \033[0m' target_ip
|
|
target_device="custom"
|
|
;;
|
|
[1-9]*)
|
|
if [ "$choice" -le "${#tailscale_devices[@]}" ]; then
|
|
selected_device="${tailscale_devices[$((choice-1))]}"
|
|
target_ip=$(echo "$selected_device" | cut -d' ' -f3)
|
|
target_device=$(echo "$selected_device" | cut -d' ' -f1)
|
|
else
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
|
|
exit 1
|
|
fi
|
|
;;
|
|
[Cc])
|
|
echo -e "${YELLOW}Operation cancelled${NC}"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Step 3: Port Configuration
|
|
print_header "Step 3: Configure Port Settings"
|
|
echo -e "${CYAN}Port Configuration Options:${NC}"
|
|
echo -e "${YELLOW}1)${NC} Use default ports only (SSH TCP 22, Tailscale UDP 41641)"
|
|
echo -e "${YELLOW}2)${NC} Configure custom ports"
|
|
echo -e "${CYAN}Press Enter to use default ports only${NC}"
|
|
|
|
read -p $'\033[0;36mSelect an option: \033[0m' port_choice
|
|
port_choice=${port_choice:-1}
|
|
|
|
declare -a PRESERVED_TCP_PORTS
|
|
declare -a PRESERVED_UDP_PORTS
|
|
|
|
case $port_choice in
|
|
1)
|
|
PRESERVED_TCP_PORTS=($DEFAULT_TCP_PORT)
|
|
PRESERVED_UDP_PORTS=($DEFAULT_UDP_PORT)
|
|
echo -e "${GREEN}Using default TCP port: ${DEFAULT_TCP_PORT}${NC}"
|
|
echo -e "${GREEN}Using default UDP port: ${DEFAULT_UDP_PORT}${NC}"
|
|
;;
|
|
2)
|
|
echo -e "\n${CYAN}Enter TCP ports to preserve (space-separated, e.g., '22 80 443')${NC}"
|
|
echo -e "${CYAN}Press Enter to use default TCP port ${DEFAULT_TCP_PORT}${NC}"
|
|
read -p $'\033[0;36mTCP Ports: \033[0m' custom_tcp_ports
|
|
|
|
if [ -z "$custom_tcp_ports" ]; then
|
|
PRESERVED_TCP_PORTS=($DEFAULT_TCP_PORT)
|
|
echo -e "${GREEN}Using default TCP port: ${DEFAULT_TCP_PORT}${NC}"
|
|
else
|
|
PRESERVED_TCP_PORTS=($custom_tcp_ports)
|
|
echo -e "${GREEN}Using custom TCP ports: ${custom_tcp_ports}${NC}"
|
|
fi
|
|
|
|
echo -e "\n${CYAN}Enter UDP ports to preserve (space-separated, e.g., '41641 53')${NC}"
|
|
echo -e "${CYAN}Press Enter to use default UDP port ${DEFAULT_UDP_PORT}${NC}"
|
|
read -p $'\033[0;36mUDP Ports: \033[0m' custom_udp_ports
|
|
|
|
if [ -z "$custom_udp_ports" ]; then
|
|
PRESERVED_UDP_PORTS=($DEFAULT_UDP_PORT)
|
|
echo -e "${GREEN}Using default UDP port: ${DEFAULT_UDP_PORT}${NC}"
|
|
else
|
|
PRESERVED_UDP_PORTS=($custom_udp_ports)
|
|
echo -e "${GREEN}Using custom UDP ports: ${custom_udp_ports}${NC}"
|
|
fi
|
|
;;
|
|
*)
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Step 4: Confirmation
|
|
print_header "Step 4: Review your selections"
|
|
source_interface=$(get_interface_name "$source_ip")
|
|
echo -e "${CYAN}Source Details:${NC}"
|
|
echo -e " IP Address: ${BOLD}$source_ip${NC}"
|
|
echo -e " Interface: ${BOLD}$source_interface${NC}"
|
|
echo -e " Subnet: ${BOLD}$source_subnet${NC}"
|
|
echo -e "\n${CYAN}Target Details:${NC}"
|
|
echo -e " IP Address: ${BOLD}$target_ip${NC}"
|
|
echo -e " Device: ${BOLD}$target_device${NC}"
|
|
echo -e "\n${CYAN}Port Configuration:${NC}"
|
|
echo -e " Preserved TCP Ports: ${BOLD}${PRESERVED_TCP_PORTS[*]}${NC}"
|
|
echo -e " Preserved UDP Ports: ${BOLD}${PRESERVED_UDP_PORTS[*]}${NC}"
|
|
echo -e "\n${YELLOW}This will forward traffic from $source_interface to tailscale0${NC}"
|
|
read -p $'\033[1;33mApply these changes? [Y/N, default is Y]: \033[0m' confirm
|
|
|
|
# Default to 'Y' if no input is provided
|
|
confirm=${confirm:-Y}
|
|
|
|
if [[ $confirm =~ ^[Yy]$ ]]; then
|
|
# Step 5: Apply iptables configuration
|
|
print_header "Step 5: Applying configuration..."
|
|
|
|
# Create backup before any changes
|
|
echo -e "${YELLOW}[1/7]${NC} Creating backup..."
|
|
mkdir -p /etc/iptables/backup
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
|
backup_file="/etc/iptables/backup/rules.v4.${timestamp}"
|
|
|
|
if ! iptables-save > "$backup_file"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create backup. Aborting configuration"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -s "$backup_file" ]; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Backup file is empty. Aborting configuration"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if IP forwarding is enabled
|
|
if [ "$(sysctl -n net.ipv4.ip_forward)" -ne 1 ]; then
|
|
echo -e "${YELLOW}[2/7]${NC} Enabling IP forwarding..."
|
|
if ! echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf || ! sysctl -w net.ipv4.ip_forward=1; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to enable IP forwarding"
|
|
exit 1
|
|
fi
|
|
# Also add to sysctl.d for persistent configuration
|
|
if ! echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-tailscale.conf; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create persistent IP forwarding configuration"
|
|
exit 1
|
|
fi
|
|
sysctl -p /etc/sysctl.d/99-tailscale.conf
|
|
else
|
|
echo -e "${YELLOW}[2/7]${NC} IP forwarding is already enabled"
|
|
fi
|
|
|
|
# Get interface name
|
|
source_interface=$(get_interface_name "$source_ip")
|
|
if [ -z "$source_interface" ]; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Could not determine interface name for IP $source_ip"
|
|
exit 1
|
|
fi
|
|
|
|
# Apply rules
|
|
echo -e "${YELLOW}[3/7]${NC} Applying new rules..."
|
|
|
|
# Clear existing NAT rules
|
|
echo -e "${CYAN}Clearing existing NAT and forwarding rules...${NC}"
|
|
iptables -t nat -F
|
|
iptables -F FORWARD
|
|
|
|
# Allow preserved TCP ports to reach the host
|
|
for port in "${PRESERVED_TCP_PORTS[@]}"; do
|
|
echo -e "${CYAN}Preserving TCP host access on port ${port}${NC}"
|
|
iptables -t nat -A PREROUTING -d "${source_ip}" -p tcp --dport ${port} -j ACCEPT
|
|
iptables -A FORWARD -p tcp --dport ${port} -j ACCEPT
|
|
done
|
|
|
|
# Allow preserved UDP ports to reach the host
|
|
for port in "${PRESERVED_UDP_PORTS[@]}"; do
|
|
echo -e "${CYAN}Preserving UDP host access on port ${port}${NC}"
|
|
iptables -t nat -A PREROUTING -d "${source_ip}" -p udp --dport ${port} -j ACCEPT
|
|
iptables -A FORWARD -p udp --dport ${port} -j ACCEPT
|
|
# Additional rule to ensure UDP responses can return
|
|
iptables -A FORWARD -p udp --sport ${port} -j ACCEPT
|
|
done
|
|
|
|
# Set up NAT rules for all other ports
|
|
echo -e "${CYAN}Setting up NAT rules...${NC}"
|
|
if ! iptables -t nat -A PREROUTING -d "${source_ip}" -j DNAT --to-destination "${target_ip}"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to set up DNAT rules"
|
|
echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous configuration..."
|
|
if ! iptables-restore < "$backup_file"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup: $backup_file"
|
|
echo -e "${RED}${BOLD}[WARNING]${NC} Your firewall rules may be in an inconsistent state"
|
|
echo -e "${RED}${BOLD}[WARNING]${NC} Please check your configuration manually"
|
|
else
|
|
echo -e "${GREEN}[SUCCESS]${NC} Successfully restored previous configuration"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
if ! iptables -t nat -A POSTROUTING -s "${target_ip}" -j SNAT --to-source "${source_ip}"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to set up SNAT rules"
|
|
echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous configuration..."
|
|
if ! iptables-restore < "$backup_file"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup"
|
|
exit 1
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Set up FORWARD chain
|
|
echo -e "${CYAN}Setting up forwarding rules...${NC}"
|
|
iptables -A FORWARD -i ${source_interface} -o tailscale0 -j ACCEPT
|
|
iptables -A FORWARD -i tailscale0 -o ${source_interface} -j ACCEPT
|
|
iptables -A FORWARD -p icmp -j ACCEPT
|
|
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
|
|
|
|
# Add Tailscale subnet handling
|
|
echo -e "${CYAN}Setting up Tailscale subnet rules...${NC}"
|
|
iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to-source "${source_ip}"
|
|
iptables -t nat -A POSTROUTING -s "${source_subnet}" -j MASQUERADE
|
|
|
|
# Verify configuration
|
|
echo -e "${YELLOW}[4/7]${NC} Verifying configuration..."
|
|
if ! iptables -t nat -L PREROUTING -n -v >/dev/null 2>&1; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Configuration verification failed. Restoring previous rules..."
|
|
if ! iptables-restore < "$backup_file"; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup"
|
|
exit 1
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Enable service
|
|
echo -e "${YELLOW}[5/7]${NC} Enabling iptables-persistent service..."
|
|
if systemctl is-active --quiet netfilter-persistent; then
|
|
systemctl restart netfilter-persistent
|
|
else
|
|
systemctl enable netfilter-persistent
|
|
systemctl start netfilter-persistent
|
|
fi
|
|
|
|
# Save current configuration
|
|
echo -e "${YELLOW}[6/7]${NC} Saving current configuration..."
|
|
if ! save_iptables; then
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to save configuration. Current rules may be lost on reboot"
|
|
exit 1
|
|
fi
|
|
|
|
# Final status
|
|
echo -e "${YELLOW}[7/7]${NC} Configuration complete"
|
|
echo -e "\n${GREEN}${BOLD}[SUCCESS]${NC} Configuration completed successfully!"
|
|
echo -e "${CYAN}Preserved TCP ports: ${PRESERVED_TCP_PORTS[*]}${NC}"
|
|
echo -e "${CYAN}Preserved UDP ports: ${PRESERVED_UDP_PORTS[*]}${NC}"
|
|
echo -e "${CYAN}All other ports are forwarded to the Tailscale device${NC}"
|
|
echo -e "${CYAN}Backup saved to: $backup_file${NC}\n"
|
|
else
|
|
echo -e "\n${YELLOW}Operation cancelled by user${NC}"
|
|
exit 1
|
|
fi |