tailscale-ip-forward-script/tailscale-forward.sh
2024-12-07 12:50:00 +05:30

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